/* eslint-disable */
// urlParams is null when used for embedding
window.urlParams = window.urlParams || {};

// Public global variables
window.MAX_REQUEST_SIZE = window.MAX_REQUEST_SIZE || 10485760;
window.MAX_AREA = window.MAX_AREA || 15000 * 15000;

// URLs for save and export
// window.EXPORT_URL = window.EXPORT_URL || '/export';
// window.SAVE_URL = window.SAVE_URL || '/save';
// window.OPEN_URL = window.OPEN_URL || '/open';
window.mxBasePath = '/diagram';
window.RESOURCES_PATH = '/diagram/resources';
// window.RESOURCE_BASE = window.RESOURCE_BASE || window.RESOURCES_PATH + '/grapheditor';
window.STENCIL_PATH = '/diagram/stencils';
window.IMAGE_PATH = '/diagram/images';
window.STYLE_PATH = '/diagram/styles';
window.CSS_PATH = '/diagram/styles';
// window.OPEN_FORM = window.OPEN_FORM || 'open.html';

var HoverIcons = function () {};
var mxCellEditorGetInitialValue = function () {};
var mxCellEditorGetCurrentValue = function () {};
var mxGraphHandlerIsValidDropTarget = function () {};
var Format = function () {};
var BaseFormatPanel = function () {};
var ArrangePanel = function () {};
var TextFormatPanel = function () {};
var StyleFormatPanel = function () {};
var DiagramStylePanel = function () {};
var DiagramFormatPanel = function () {};
var Menus = function () {};

/**
 * jscolor, JavaScript Color Picker
 *
 * @version 1.3.13
 * @license GNU Lesser General Public License, http://www.gnu.org/copyleft/lesser.html
 * @author  Jan Odvarko, http://odvarko.cz
 * @created 2008-06-15
 * @updated 2012-01-19
 * @link    http://jscolor.com
 */

var mxJSColor = {
  dir: '', // location of jscolor directory (leave empty to autodetect)
  bindClass: 'color', // class name
  binding: true, // automatic binding via <input class="...">
  preloading: true, // use image preloading?

  install: function () {
    //mxJSColor.addEvent(window, 'load', mxJSColor.init);
  },

  init: function () {
    if (mxJSColor.preloading) {
      mxJSColor.preload();
    }
  },

  getDir: function () {
    if (!mxJSColor.dir) {
      var detected = mxJSColor.detectDir();
      mxJSColor.dir = detected !== false ? detected : 'jscolor/';
    }
    return mxJSColor.dir;
  },

  detectDir: function () {
    var base = location.href;

    var e = document.getElementsByTagName('base');
    for (var i = 0; i < e.length; i += 1) {
      if (e[i].href) {
        base = e[i].href;
      }
    }

    var e = document.getElementsByTagName('script');
    for (var i = 0; i < e.length; i += 1) {
      if (e[i].src && /(^|\/)jscolor\.js([?#].*)?$/i.test(e[i].src)) {
        var src = new mxJSColor.URI(e[i].src);
        var srcAbs = src.toAbsolute(base);
        srcAbs.path = srcAbs.path.replace(/[^\/]+$/, ''); // remove filename
        srcAbs.query = null;
        srcAbs.fragment = null;
        return srcAbs.toString();
      }
    }
    return false;
  },

  preload: function () {
    for (var fn in mxJSColor.imgRequire) {
      if (mxJSColor.imgRequire.hasOwnProperty(fn)) {
        mxJSColor.loadImage(fn);
      }
    }
  },

  images: {
    pad: [181, 101],
    sld: [16, 101],
    cross: [15, 15],
    arrow: [7, 11],
  },

  imgRequire: {},
  imgLoaded: {},

  requireImage: function (filename) {
    mxJSColor.imgRequire[filename] = true;
  },

  loadImage: function (filename) {
    if (!mxJSColor.imgLoaded[filename]) {
      mxJSColor.imgLoaded[filename] = new Image();
      mxJSColor.imgLoaded[filename].src = mxJSColor.getDir() + filename;
    }
  },

  fetchElement: function (mixed) {
    return typeof mixed === 'string' ? document.getElementById(mixed) : mixed;
  },

  addEvent: function (el, evnt, func) {
    if (el.addEventListener) {
      el.addEventListener(evnt, func, false);
    } else if (el.attachEvent) {
      el.attachEvent('on' + evnt, func);
    }
  },

  fireEvent: function (el, evnt) {
    if (!el) {
      return;
    }
    if (document.createEvent) {
      var ev = document.createEvent('HTMLEvents');
      ev.initEvent(evnt, true, true);
      el.dispatchEvent(ev);
    } else if (document.createEventObject) {
      var ev = document.createEventObject();
      el.fireEvent('on' + evnt, ev);
    } else if (el['on' + evnt]) {
      // alternatively use the traditional event model (IE5)
      el['on' + evnt]();
    }
  },

  getElementPos: function (e) {
    var e1 = e,
      e2 = e;
    var x = 0,
      y = 0;
    if (e1.offsetParent) {
      do {
        x += e1.offsetLeft;
        y += e1.offsetTop;
      } while ((e1 = e1.offsetParent));
    }
    while ((e2 = e2.parentNode) && e2.nodeName.toUpperCase() !== 'BODY') {
      x -= e2.scrollLeft;
      y -= e2.scrollTop;
    }
    return [x, y];
  },

  getElementSize: function (e) {
    return [e.offsetWidth, e.offsetHeight];
  },

  getRelMousePos: function (e) {
    var x = 0,
      y = 0;
    if (!e) {
      e = window.event;
    }
    if (typeof e.offsetX === 'number') {
      x = e.offsetX;
      y = e.offsetY;
    } else if (typeof e.layerX === 'number') {
      x = e.layerX;
      y = e.layerY;
    }
    return { x: x, y: y };
  },

  getViewPos: function () {
    if (typeof window.pageYOffset === 'number') {
      return [window.pageXOffset, window.pageYOffset];
    } else if (document.body && (document.body.scrollLeft || document.body.scrollTop)) {
      return [document.body.scrollLeft, document.body.scrollTop];
    } else if (
      document.documentElement &&
      (document.documentElement.scrollLeft || document.documentElement.scrollTop)
    ) {
      return [document.documentElement.scrollLeft, document.documentElement.scrollTop];
    } else {
      return [0, 0];
    }
  },

  getViewSize: function () {
    if (typeof window.innerWidth === 'number') {
      return [window.innerWidth, window.innerHeight];
    } else if (document.body && (document.body.clientWidth || document.body.clientHeight)) {
      return [document.body.clientWidth, document.body.clientHeight];
    } else if (
      document.documentElement &&
      (document.documentElement.clientWidth || document.documentElement.clientHeight)
    ) {
      return [document.documentElement.clientWidth, document.documentElement.clientHeight];
    } else {
      return [0, 0];
    }
  },

  URI: function (uri) {
    // See RFC3986

    this.scheme = null;
    this.authority = null;
    this.path = '';
    this.query = null;
    this.fragment = null;

    this.parse = function (uri) {
      var m = uri.match(/^(([A-Za-z][0-9A-Za-z+.-]*)(:))?((\/\/)([^\/?#]*))?([^?#]*)((\?)([^#]*))?((#)(.*))?/);
      this.scheme = m[3] ? m[2] : null;
      this.authority = m[5] ? m[6] : null;
      this.path = m[7];
      this.query = m[9] ? m[10] : null;
      this.fragment = m[12] ? m[13] : null;
      return this;
    };

    this.toString = function () {
      var result = '';
      if (this.scheme !== null) {
        result = result + this.scheme + ':';
      }
      if (this.authority !== null) {
        result = result + '//' + this.authority;
      }
      if (this.path !== null) {
        result = result + this.path;
      }
      if (this.query !== null) {
        result = result + '?' + this.query;
      }
      if (this.fragment !== null) {
        result = result + '#' + this.fragment;
      }
      return result;
    };

    this.toAbsolute = function (base) {
      var base = new mxJSColor.URI(base);
      var r = this;
      var t = new mxJSColor.URI();

      if (base.scheme === null) {
        return false;
      }

      if (r.scheme !== null && r.scheme.toLowerCase() === base.scheme.toLowerCase()) {
        r.scheme = null;
      }

      if (r.scheme !== null) {
        t.scheme = r.scheme;
        t.authority = r.authority;
        t.path = removeDotSegments(r.path);
        t.query = r.query;
      } else {
        if (r.authority !== null) {
          t.authority = r.authority;
          t.path = removeDotSegments(r.path);
          t.query = r.query;
        } else {
          if (r.path === '') {
            // TODO: == or === ?
            t.path = base.path;
            if (r.query !== null) {
              t.query = r.query;
            } else {
              t.query = base.query;
            }
          } else {
            if (r.path.substr(0, 1) === '/') {
              t.path = removeDotSegments(r.path);
            } else {
              if (base.authority !== null && base.path === '') {
                // TODO: == or === ?
                t.path = '/' + r.path;
              } else {
                t.path = base.path.replace(/[^\/]+$/, '') + r.path;
              }
              t.path = removeDotSegments(t.path);
            }
            t.query = r.query;
          }
          t.authority = base.authority;
        }
        t.scheme = base.scheme;
      }
      t.fragment = r.fragment;

      return t;
    };

    function removeDotSegments(path) {
      var out = '';
      while (path) {
        if (path.substr(0, 3) === '../' || path.substr(0, 2) === './') {
          path = path.replace(/^\.+/, '').substr(1);
        } else if (path.substr(0, 3) === '/./' || path === '/.') {
          path = '/' + path.substr(3);
        } else if (path.substr(0, 4) === '/../' || path === '/..') {
          path = '/' + path.substr(4);
          out = out.replace(/\/?[^\/]*$/, '');
        } else if (path === '.' || path === '..') {
          path = '';
        } else {
          var rm = path.match(/^\/?[^\/]*/)[0];
          path = path.substr(rm.length);
          out = out + rm;
        }
      }
      return out;
    }

    if (uri) {
      this.parse(uri);
    }
  },

  /*
   * Usage example:
   * var myColor = new mxJSColor.color(myInputElement)
   */

  color: function (target, prop) {
    this.required = true; // refuse empty values?
    this.adjust = true; // adjust value to uniform notation?
    this.hash = false; // prefix color with # symbol?
    this.caps = true; // uppercase?
    this.slider = true; // show the value/saturation slider?
    this.valueElement = target; // value holder
    this.styleElement = target; // where to reflect current color
    this.onImmediateChange = null; // onchange callback (can be either string or function)
    this.hsv = [0, 0, 1]; // read-only  0-6, 0-1, 0-1
    this.rgb = [1, 1, 1]; // read-only  0-1, 0-1, 0-1

    this.pickerOnfocus = true; // display picker on focus?
    this.pickerMode = 'HSV'; // HSV | HVS
    this.pickerPosition = 'bottom'; // left | right | top | bottom
    this.pickerSmartPosition = true; // automatically adjust picker position when necessary
    this.pickerButtonHeight = 20; // px
    this.pickerClosable = false;
    this.pickerCloseText = 'Close';
    this.pickerButtonColor = 'ButtonText'; // px
    this.pickerFace = 0; // px
    this.pickerFaceColor = 'ThreeDFace'; // CSS color
    this.pickerBorder = 1; // px
    this.pickerBorderColor = 'ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight'; // CSS color
    this.pickerInset = 1; // px
    this.pickerInsetColor = 'ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow'; // CSS color
    this.pickerZIndex = 10000;

    for (var p in prop) {
      if (prop.hasOwnProperty(p)) {
        this[p] = prop[p];
      }
    }

    this.hidePicker = function () {
      if (isPickerOwner()) {
        removePicker();
      }
    };

    this.showPicker = function () {
      if (!isPickerOwner()) {
        var tp = mxJSColor.getElementPos(target); // target pos
        var ts = mxJSColor.getElementSize(target); // target size
        var vp = mxJSColor.getViewPos(); // view pos
        var vs = mxJSColor.getViewSize(); // view size
        var ps = getPickerDims(this); // picker size
        var a, b, c;
        switch (this.pickerPosition.toLowerCase()) {
          case 'left':
            a = 1;
            b = 0;
            c = -1;
            break;
          case 'right':
            a = 1;
            b = 0;
            c = 1;
            break;
          case 'top':
            a = 0;
            b = 1;
            c = -1;
            break;
          default:
            a = 0;
            b = 1;
            c = 1;
            break;
        }
        var l = (ts[b] + ps[b]) / 2;

        // picker pos
        if (!this.pickerSmartPosition) {
          var pp = [tp[a], tp[b] + ts[b] - l + l * c];
        } else {
          var pp = [
            -vp[a] + tp[a] + ps[a] > vs[a]
              ? -vp[a] + tp[a] + ts[a] / 2 > vs[a] / 2 && tp[a] + ts[a] - ps[a] >= 0
                ? tp[a] + ts[a] - ps[a]
                : tp[a]
              : tp[a],
            -vp[b] + tp[b] + ts[b] + ps[b] - l + l * c > vs[b]
              ? -vp[b] + tp[b] + ts[b] / 2 > vs[b] / 2 && tp[b] + ts[b] - l - l * c >= 0
                ? tp[b] + ts[b] - l - l * c
                : tp[b] + ts[b] - l + l * c
              : tp[b] + ts[b] - l + l * c >= 0
              ? tp[b] + ts[b] - l + l * c
              : tp[b] + ts[b] - l - l * c,
          ];
        }
        drawPicker(0, 0);
      }
    };

    this.importColor = function () {
      if (!valueElement) {
        this.exportColor();
      } else {
        if (!this.adjust) {
          if (!this.fromString(valueElement.value, leaveValue)) {
            styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage;
            styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
            styleElement.style.color = styleElement.jscStyle.color;
            this.exportColor(leaveValue | leaveStyle);
          }
        } else if (!this.required && /^\s*$/.test(valueElement.value)) {
          valueElement.value = '';
          styleElement.style.backgroundImage = styleElement.jscStyle.backgroundImage;
          styleElement.style.backgroundColor = styleElement.jscStyle.backgroundColor;
          styleElement.style.color = styleElement.jscStyle.color;
          this.exportColor(leaveValue | leaveStyle);
        } else if (this.fromString(valueElement.value)) {
          // OK
        } else {
          this.exportColor();
        }
      }
    };

    this.exportColor = function (flags) {
      if (!(flags & leaveValue) && valueElement) {
        var value = this.toString();
        if (this.caps) {
          value = value.toUpperCase();
        }
        if (this.hash) {
          value = '#' + value;
        }
        valueElement.value = value;
      }
      if (!(flags & leaveStyle) && styleElement) {
        styleElement.style.backgroundImage = 'none';
        styleElement.style.backgroundColor = '#' + this.toString();
        styleElement.style.color =
          0.213 * this.rgb[0] + 0.715 * this.rgb[1] + 0.072 * this.rgb[2] < 0.5 ? '#FFF' : '#000';
      }
      if (!(flags & leavePad) && isPickerOwner()) {
        redrawPad();
      }
      if (!(flags & leaveSld) && isPickerOwner()) {
        redrawSld();
      }
    };

    this.fromHSV = function (h, s, v, flags) {
      // null = don't change
      (h < 0 && (h = 0)) || (h > 6 && (h = 6));
      (s < 0 && (s = 0)) || (s > 1 && (s = 1));
      (v < 0 && (v = 0)) || (v > 1 && (v = 1));
      this.rgb = HSV_RGB(
        h === null ? this.hsv[0] : (this.hsv[0] = h),
        s === null ? this.hsv[1] : (this.hsv[1] = s),
        v === null ? this.hsv[2] : (this.hsv[2] = v)
      );
      this.exportColor(flags);
    };

    this.fromRGB = function (r, g, b, flags) {
      // null = don't change
      (r < 0 && (r = 0)) || (r > 1 && (r = 1));
      (g < 0 && (g = 0)) || (g > 1 && (g = 1));
      (b < 0 && (b = 0)) || (b > 1 && (b = 1));
      var hsv = RGB_HSV(
        r === null ? this.rgb[0] : (this.rgb[0] = r),
        g === null ? this.rgb[1] : (this.rgb[1] = g),
        b === null ? this.rgb[2] : (this.rgb[2] = b)
      );
      if (hsv[0] !== null) {
        this.hsv[0] = hsv[0];
      }
      if (hsv[2] !== 0) {
        this.hsv[1] = hsv[1];
      }
      this.hsv[2] = hsv[2];
      this.exportColor(flags);
    };

    this.fromString = function (hex, flags) {
      var m = hex.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i);
      if (!m) {
        return false;
      } else {
        if (m[1].length === 6) {
          // 6-char notation
          this.fromRGB(
            parseInt(m[1].substr(0, 2), 16) / 255,
            parseInt(m[1].substr(2, 2), 16) / 255,
            parseInt(m[1].substr(4, 2), 16) / 255,
            flags
          );
        } else {
          // 3-char notation
          this.fromRGB(
            parseInt(m[1].charAt(0) + m[1].charAt(0), 16) / 255,
            parseInt(m[1].charAt(1) + m[1].charAt(1), 16) / 255,
            parseInt(m[1].charAt(2) + m[1].charAt(2), 16) / 255,
            flags
          );
        }
        return true;
      }
    };

    this.toString = function () {
      return (
        (0x100 | Math.round(255 * this.rgb[0])).toString(16).substr(1) +
        (0x100 | Math.round(255 * this.rgb[1])).toString(16).substr(1) +
        (0x100 | Math.round(255 * this.rgb[2])).toString(16).substr(1)
      );
    };

    function RGB_HSV(r, g, b) {
      var n = Math.min(Math.min(r, g), b);
      var v = Math.max(Math.max(r, g), b);
      var m = v - n;
      if (m === 0) {
        return [null, 0, v];
      }
      var h = r === n ? 3 + (b - g) / m : g === n ? 5 + (r - b) / m : 1 + (g - r) / m;
      return [h === 6 ? 0 : h, m / v, v];
    }

    function HSV_RGB(h, s, v) {
      if (h === null) {
        return [v, v, v];
      }
      var i = Math.floor(h);
      var f = i % 2 ? h - i : 1 - (h - i);
      var m = v * (1 - s);
      var n = v * (1 - s * f);
      switch (i) {
        case 6:
        case 0:
          return [v, n, m];
        case 1:
          return [n, v, m];
        case 2:
          return [m, v, n];
        case 3:
          return [m, n, v];
        case 4:
          return [n, m, v];
        case 5:
          return [v, m, n];
      }
    }

    function removePicker() {
      delete mxJSColor.picker.owner;
      document.getElementsByTagName('body')[0].removeChild(mxJSColor.picker.boxB);
    }

    function drawPicker(x, y) {
      if (!mxJSColor.picker) {
        mxJSColor.picker = {
          box: document.createElement('div'),
          boxB: document.createElement('div'),
          pad: document.createElement('div'),
          padB: document.createElement('div'),
          padM: document.createElement('div'),
          sld: document.createElement('div'),
          sldB: document.createElement('div'),
          sldM: document.createElement('div'),
          btn: document.createElement('div'),
          btnS: document.createElement('span'),
          btnT: document.createTextNode(THIS.pickerCloseText),
        };
        for (var i = 0, segSize = 4; i < mxJSColor.images.sld[1]; i += segSize) {
          var seg = document.createElement('div');
          seg.style.height = segSize + 'px';
          seg.style.fontSize = '1px';
          seg.style.lineHeight = '0';
          mxJSColor.picker.sld.appendChild(seg);
        }
        mxJSColor.picker.sldB.appendChild(mxJSColor.picker.sld);
        mxJSColor.picker.box.appendChild(mxJSColor.picker.sldB);
        mxJSColor.picker.box.appendChild(mxJSColor.picker.sldM);
        mxJSColor.picker.padB.appendChild(mxJSColor.picker.pad);
        mxJSColor.picker.box.appendChild(mxJSColor.picker.padB);
        mxJSColor.picker.box.appendChild(mxJSColor.picker.padM);
        mxJSColor.picker.btnS.appendChild(mxJSColor.picker.btnT);
        mxJSColor.picker.btn.appendChild(mxJSColor.picker.btnS);
        mxJSColor.picker.box.appendChild(mxJSColor.picker.btn);
        mxJSColor.picker.boxB.appendChild(mxJSColor.picker.box);
      }

      var p = mxJSColor.picker;

      // controls interaction
      p.box.onmouseup = p.box.onmouseout = function () {
        if (!mxClient.IS_TOUCH) {
          target.focus();
        }
      };
      p.box.onmousedown = function () {
        abortBlur = true;
      };
      p.box.onmousemove = function (e) {
        if (holdPad || holdSld) {
          holdPad && setPad(e);
          holdSld && setSld(e);
          if (document.selection) {
            document.selection.empty();
          } else if (window.getSelection) {
            window.getSelection().removeAllRanges();
          }
          dispatchImmediateChange();
        }
      };
      p.padM.onmouseup = p.padM.onmouseout = function () {
        if (holdPad) {
          holdPad = false;
          mxJSColor.fireEvent(valueElement, 'change');
        }
      };
      p.padM.onmousedown = function (e) {
        // if the slider is at the bottom, move it up
        switch (modeID) {
          case 0:
            if (THIS.hsv[2] === 0) {
              THIS.fromHSV(null, null, 1.0);
            }
            break;
          case 1:
            if (THIS.hsv[1] === 0) {
              THIS.fromHSV(null, 1.0, null);
            }
            break;
        }
        holdPad = true;
        setPad(e);
        dispatchImmediateChange();
      };
      p.sldM.onmouseup = p.sldM.onmouseout = function () {
        if (holdSld) {
          holdSld = false;
          mxJSColor.fireEvent(valueElement, 'change');
        }
      };
      p.sldM.onmousedown = function (e) {
        holdSld = true;
        setSld(e);
        dispatchImmediateChange();
      };

      // picker
      var dims = getPickerDims(THIS);
      p.box.style.width = dims[0] + 'px';
      p.box.style.height = dims[1] + 'px';

      // picker border
      p.boxB.style.position = 'absolute';
      p.boxB.style.clear = 'both';
      p.boxB.style.left = x + 'px';
      p.boxB.style.top = y + 'px';
      p.boxB.style.zIndex = THIS.pickerZIndex;
      p.boxB.style.border = THIS.pickerBorder + 'px solid';
      p.boxB.style.borderColor = THIS.pickerBorderColor;
      p.boxB.style.background = THIS.pickerFaceColor;

      // pad image
      p.pad.style.width = mxJSColor.images.pad[0] + 'px';
      p.pad.style.height = mxJSColor.images.pad[1] + 'px';

      // pad border
      p.padB.style.position = 'absolute';
      p.padB.style.left = THIS.pickerFace + 'px';
      p.padB.style.top = THIS.pickerFace + 'px';
      p.padB.style.border = THIS.pickerInset + 'px solid';
      p.padB.style.borderColor = THIS.pickerInsetColor;

      // pad mouse area
      p.padM.style.position = 'absolute';
      p.padM.style.left = '0';
      p.padM.style.top = '0';
      p.padM.style.width =
        THIS.pickerFace + 2 * THIS.pickerInset + mxJSColor.images.pad[0] + mxJSColor.images.arrow[0] + 'px';
      p.padM.style.height = p.box.style.height;
      p.padM.style.cursor = 'crosshair';

      // slider image
      p.sld.style.overflow = 'hidden';
      p.sld.style.width = mxJSColor.images.sld[0] + 'px';
      p.sld.style.height = mxJSColor.images.sld[1] + 'px';

      // slider border
      p.sldB.style.display = THIS.slider ? 'block' : 'none';
      p.sldB.style.position = 'absolute';
      p.sldB.style.right = THIS.pickerFace + 'px';
      p.sldB.style.top = THIS.pickerFace + 'px';
      p.sldB.style.border = THIS.pickerInset + 'px solid';
      p.sldB.style.borderColor = THIS.pickerInsetColor;

      // slider mouse area
      p.sldM.style.display = THIS.slider ? 'block' : 'none';
      p.sldM.style.position = 'absolute';
      p.sldM.style.right = '0';
      p.sldM.style.top = '0';
      p.sldM.style.width =
        mxJSColor.images.sld[0] + mxJSColor.images.arrow[0] + THIS.pickerFace + 2 * THIS.pickerInset + 'px';
      p.sldM.style.height = p.box.style.height;
      try {
        p.sldM.style.cursor = 'pointer';
      } catch (eOldIE) {
        p.sldM.style.cursor = 'hand';
      }

      // "close" button
      function setBtnBorder() {
        var insetColors = THIS.pickerInsetColor.split(/\s+/);
        var pickerOutsetColor =
          insetColors.length < 2
            ? insetColors[0]
            : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1];
        p.btn.style.borderColor = pickerOutsetColor;
      }
      p.btn.style.display = THIS.pickerClosable ? 'block' : 'none';
      p.btn.style.position = 'absolute';
      p.btn.style.left = THIS.pickerFace + 'px';
      p.btn.style.bottom = THIS.pickerFace + 'px';
      p.btn.style.padding = '0 15px';
      p.btn.style.height = '18px';
      p.btn.style.border = THIS.pickerInset + 'px solid';
      setBtnBorder();
      p.btn.style.color = THIS.pickerButtonColor;
      p.btn.style.font = '12px sans-serif';
      p.btn.style.textAlign = 'center';
      try {
        p.btn.style.cursor = 'pointer';
      } catch (eOldIE) {
        p.btn.style.cursor = 'hand';
      }
      p.btn.onmousedown = function () {
        THIS.hidePicker();
      };
      p.btnS.style.lineHeight = p.btn.style.height;

      // load images in optimal order
      switch (modeID) {
        case 0:
          var padImg = 'hs';
          break;
        case 1:
          var padImg = 'hv';
          break;
      }

      const bgs = {
        arrow:
          "url('data:image/gif;base64,R0lGODlhBwALAKECAAAAAP///6g8eKg8eCH5BAEKAAIALAAAAAAHAAsAAAITTIQYcLnsgGxvijrxqdQq6DRJAQA7')",
        cross:
          "url('data:image/gif;base64,R0lGODlhDwAPAKEBAAAAAP///////////yH5BAEKAAIALAAAAAAPAA8AAAIklB8Qx53b4otSUWcvyiz4/4AeQJbmKY4p1HHapBlwPL/uVRsFADs=')",
        hs: "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALUAAABlCAIAAACEDzXRAAAKQ0lEQVR42u2d23IjKwxFBeRh5v8/9uQlzXlI2gGELoCEm6pxubp6PI69WoV3C20uIQPAH4A/AH/p41/pDcTxP4BPGDtq3vYJGPpv8craeRdo/fyTI8ZhHnodOGgcV/3rnx8QI0SABO2xfAbiCfVR8Wj+CD8jenbRJNyGOxLoAaH1iLu4EUEHAv1+MsQ4zLE4AiIOcpibv4u9OET6+cP4ASnJ1JH4/G60A9km+GbBxLlpKAkADoR2JnaBrvWD/x1GCZn9EYLEHqVfI6Efx0D7E9tDF/rBSJ54l8FqTUse0J+EvxPrdK0fD4aOLXQi7zyGxMaRvvWjjH2U8o9I44OKnf+wyCpHoR/Phk6d/COq8481YrNI3/qR2B+kMl1Fl6J8o/JrU/GEA6HdiB2ha/0YBVx7rFwNHAj9JuIl6EI/qKyJug4Yvg7xA0QEpB8nQe8itoQu9EOT8M3mTsqUSUzykH6cBO1PbA9d91+ShDzV9xrtcgW21IT6L0yfcS90FKCdw+wS6Q9ISWjVfLTVVT2mdiPGGRduFKWm7dBStWlLmI0j/QExdgp7gT5qCu2BLKiLqtc94hI7PB8auQP4KuyIvSJ960fUqXWcd2F48Khr1bHSj2dDp/afmg7tMrFxpG/9mHMFuqrH3spFw0s0Bgr9OAzamdgFutaPpMuqo6WVEXWJdSL14xhof2J76MKfS2yXWGOVB1WXXGOVdxFSx587CXoLsTE0689RH7VW4RMLeKI9wPpzz4XeS2wDzdbXR52AWStjwiSAA6HdiB2h1f6cqT0whzzuzz0LejuxATTy90Xt40v7CzdGRvKS4O/zQ0AiXaRW5x/dv4vS4A/a308LYR5pFquRRvlHIrKWoCj5woCVId4eR/KPNDjURg2tdEC7rSQN5B9DYR70X5YivVxft7AyrOvrD4V2JnaBJurr+qpemLEywlRhT6qvPx16C7ExdK0fSjcj0maAzsqgBt7rfIxGP46B9iT2gi70I842bNj0U4wd/WAG/L5VP3p0W4iNoYv6Oh6eoJmSMW5laIzF8iT1TtgeV9RZoZ7QvdEUzsQu0CP+HOMKDFoZvDFg6s89CNqf2B5a4e8Hts84W0pgeohBtspBXYp8H3Tk/H23MBtHuvbn5mp7dlU9TUkP+XNJMabzHdCIbnuYDSItzZ+bdgUWrAwNPhwInRxw1/wXuYGj+4vo7flboRpjEQ6EjgojYK9/K04NFfNTauEBypXQWQLAfqQoHl9CfvpQ6C3ExtC9/q2YW2u6X1JWzfsYFPKX3L99LrQ/sT10XR+jKh+UN2AxlDOyXfLQaxxf/fkvT4d2JnaBZv258M6h4IFu2Em7/sezoLcQG0Mjfy6xrqLnVBLGWHw1i69WP5LCCn0TNEL3DLNXpCV/n5oo5jAVLY5Em6i8Pwm6uSNuCrNxpNX+nDira20qqyZriv384xhoZ2IX6Lr/wg8eiwpXYGQqPEgjsBrX+er3X46B9ie2h6bX/4i6IfcLS2mIY++7TfoCuLj6hwgN89CwBJ3Gx8rOVsn0kQ48tDS/4X1L8SzMb3BbkGdtwsD05AbP9YOU8xui+uljZei/P/3qx2HQbsSO0Ar/lveHLZYCVK4GeN1P5N+eAb2R2Aya9ueUY+8dlhLtdhWvgfEfz4AeGf9hR2wcaeTPpacvRdzLTx8J/Qq1Ij995PrJ3/mp0p8LO5YyF0s215g/9yxoZ2IXaLo+FiwtgTlvIAznp4+G3khsBk3Pf5mYvqOwMqZnNnw3i9cRDoT2JPaCZuvreskDuXkHxfQMvfbBgdBbiI2hC/3QLCg6lESZZkrfJ7nSj3gWNEMcRkYJsf5L0BXENBl1WV8/aquMLM9/eSJ0UtxZmFGGg4MigZ75ovHnvsOcUX09zRbzjCrU/K/Rev0gT2i2vu4cZrNI1+sH2e49Yb3lRC6e9fSS9ETohjj7hdkx0rQ/57/rxIrhBQdCv494Hlrtz1lo30Qjz1z+cRh02ndTNIMm1j8NxtmePs+76i+/esJHrH/6dGh/YntoRf9l2RtQWgJXzY4bdtbqx0OhnYldoHv7I79jf4+rOGGaRWr14yToLcTG0Ky/v32rjAsdX9S5vgI4ENqT2Asa7Z/9vq0yStiGNNVHkOe/RGmqwBQ0jEDHFpoJ85BLN+jPaRoHGWl2/2yx3msxVRHjZ0Td4GfV/Bf9bCM1dBiBfrHeJ1HR6dKEedDf5yPdBLilp/efC3Ribb1VxlUHGQe8iTb0598+Hdqf2B6a3b9SOerezsoIBWaJ3IBn1fp0j4PeQmwMzc5/idLK3UZWRkmKX2zA4Vc/DoPeSGwGrdvfY24RpKmVjrLuCQdCuxE7Qkv64TZ3Z44dVPrxUOh3EK9C1/lHGpz7ZzpVsYTF7K8Hyj+Ogd5FbAk9uL9H9LIyYg821sjo13gYtD+xPbRU/9Ds/b1sZTTIr5OStMGHA6GdiV2gUf2UmQcfJdUbXOoe6HoN1A0b2uQD4EBosXJqQTwKnXvXAEX+oZlfGaS9ewetDGCLeV1exH4e9Mr+2TDpv2iggYFW+LdBsTbngv/yUroGH0jleEX7MOgtxMbQ7PzKoMuajKyM8pYIiB194HnQzsQu0Gp/LvpulVHylsgK/TgG2p/YHrqXf6T3bJUBhfxFWUDPg95CbAytm/+ycasM9WOxWu0PnTniLWE2iLRi/4Z3bJXRvY76s0+Arl93I3aMNOHPBce2PdSkL7Kpnwe9l9gGutCPoFgkUtytXAGbex0vKCznSOs00o+ToLcQG0N/QErCrG6jrTIynVUDwo896vCzssPrNnAYtD+xPfR9fwmKeu/CVhm5OAn1STPkvhxBW1KXjeP60Y/DoJ2JXaAL/RCPy+PXc80L9d7OV294dVPbC/fb4EDoLcTG0IV+TCDrrIxMHIFg512BWj9OgvYk9oK+9SMoZG55q/JMiGS40/sLjacu28T3MVfd0Aha+u3QJXr+0Q//MBtHutaPQOzh3H0RBoZS5OJ/MjqJ9x2vQb7qgSuvX0StH0+FLptzrvTDh9gl0oV+iAbi2lAsqNmbK3yBAyLNdbRzW8Y6Btqf2B76A2IURE2zZY16KGfZlwqofJSL4feABka+xCNX+ekx0LuILaFr/Zit7s4ZA7y5Qg2sDr/t4zDodxCvQt/6occctDKGriMPzH85D9qN2BG61g/QwcLSVDSGl78C6LePY6A3EptBf0BKcjWWH/GqyJqoxKnMufEVBLJxAJwJ7U9sDH3fX0CxnIB4orMyuilTyRh6g+0bfDgQ2p/YHrrQD+jV6qb/SdRrMit8TRWwyEabxgFwJrQnsQt0rR+LR3VqFNiGHRBv73ge9EZiM+haPyicuRcVmIxzAMTMl9wfi3sGtBuxF3ShH92C29yJTteU+ohbRrZk3Qe9hdgY+tYPnOqanA+mTN33ZGKE06HQbsQu0LV+MF8+94rU5dK8B8/KyJaI+6D9ie2hC/3Q12QnSnYLn5p9P/6d0D7EltAh5wz/Hv8exON/LUjHOuz5CksAAAAASUVORK5CYII=')",
        hv: "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALUAAABlCAIAAACEDzXRAAAK+ElEQVR42u2d7ZLkrA2FD55N5d2q3P+tZv8A+fFu92JAXyD1mCRdXS6Pd7bnsQqrhY4EqQL4C/gL+Ekff0q/QBz/DfyC7aj5tV8YoX82V/bOp0D757844tHMpuvgoEe76q//+pExe9X7scyO3TtPjtPL3bv74Dr8cd2r/X/tu9zPp/T5ds7gtp8xfnyH0BmTJQZhh6mlR5NLNi4zm5Th709M3oyPytq5DoOjStRZRi6EUabWrtxAPgY6gBgIg/5RMBvpDH6RnsaGk/cZhX4Ix+ewcp7jMGhvYiAMevh+qQP+lL1I7q95FMsMv8689Qg+emvWVXtDlyjoHeLcEwNhlr77D9DIhcAfzNteoZALwVtodnUI4gqdY6HXiEd7IszSPzIzgKs6UhoN/noUM7gHkr8JdrSfBx1GDIRBE/6DH+TqG1qm236dB70zbhAGPZvfUuELRVrJsZ3ZSSJ1E1ix/HnQfsRAGPQwf9FETWKQd38UNQHfRrR3HrQ3MRAGPeQ/9LOuyqaa7lOBLPGuzhYXoMs6dHGADjAzEGbp2fyWYhftPORuihQyMeBSZmwZuq5DVzdoVzMDYZZ+fb/U2ZHP7VE2b1J5Y1aP+QAmq0ek28+DdiEeJYvZNR9o2n9AQc3PyjP3j0UBDrP/OAZ6h3i8Ad2EdgW6iU+rTiFQagOvR3FNEpi6PCn+OAM6gBgIg777DyZ2KrrYOs8fxawLqYuz/vJQaG9iIAx6yI9Vi1Q+Hd65l7oy++saqbxqUwnHQLsSA2HQRP0HFDk8USHIshggpvo8kpCPhvYgBsKgfxS9MGDXCbxkgA395UHQYcRAGDTtP7BKbZS6vAWN86C3iZX63Ao0HX8wcy/G66n1/SoVJmBRygAtZVTVFFcZ6hU2Q70XNPFmzoK+nx0tTcxfNLm9QiSZFPVBVRIGYNNfxG9zZpRYoKtOzVBXnq6Z2Rh/bFnaor98Nr/upL88DjqAeDO/zuovhREG6lJuj01V61N6dVF/OQPalVhZn7wC/fIfVToWs45BtQqIHybisGrGo6FjiKn+Bgdou/+w+D69y/sm/5Fl6Pws/zGlQxg0EX+IuZvxJE9OqH+pdMPRdEKg1l+gG9FsQUVRNEdp6mx0+osHMRAGzfa/8NqAn9TFSAJ2/eUAaG9ivT5nhib6X/iSSGVuL2tTepraSEv+4wDoHeIs6PvZEZqtX7dm9XKfx5tdMyT2XPOnz4XeSZ4O+txaCpWvX68B2oCuFe3j+ssedNmCrgHQUn5s19Ib+q1CWxSbFD+u3+5BV39o5WjgJVyEWZqt/wDbkye6vy+bvyusmqGWMs6AdiUGwqDZ+rGqm+KO1F/CVFEzud2Yvzwd2ptY7H9Zh1bXnxZ2Vl5n1F/azMcoDDjVnz4ROoAYCIPerl+v9NjOqlUpvqN+/fuhXYmV63/s1K/rGzOqQjj6uj2KeSO/7tT/8hRoF+KGtfMf/LpYK9D2/jlGfu68Xp4n1wv9SWH9c4+D3iG2+I9daCL+qIrEjRhbf6n0ObGla7v/9lnQAcRAGPQwfxG1AbGE7JpMBcTiMVESMPbvPxramxgIg57l103JPCpwurhUQp2x79X8Kutm96D5JBNs0HozV4EYCLM0W7+un6TvSV1PWoonbyXXY6C3+2831w/ybcZoxna2/I8w/eUp0GHEQBi0Zf1C5WqA1+tdBFVR8xe21y98NLQTsajfrkNvrH9adl21UnV2Xf/0KdA7xPk1LHT1H1vQrusnv8FfwzsTqYQnrZ/8DdCO6ydff75fotdP1i8MLmZtLoPUJbYaOa2//hToAGKlPrcCTccfenmg2kK96qm8nA3tRAyEQRP9L2stO2/e11HcdWJtbSydeZ8IHUMMhEET/S/wcX/7/g6qB/I8aFdiIAz6Hn8ou3Y0UZP0VV43lueUusiXWJH++I8C1SqoS9BKMxddOE3EH5mW+DXpsmqc3xbjrOtva6c+qrZOvALmt2K1bxJaSSL39yhGfe5lZihq7fkqQ7LEUJ0f216KJ69m8vzS6h9cPyiSuMt/FLn/ZR1anV9XJnvT7Z1jNp7wHA08dPaE9t3co4VGmKUV+3tsCF7ftLnHqdDLoydOTlTs76EhTeRX+ae89XnQfsRKfW4Feml/jwJcd/xr5vvKSslK2P4eD4L2JgbCoO37e1x3/HFsJ9Wj6KRmnAcdQAyEQbPrw4CmvljefHsU14QBv/rkJ0K7Emvyp6v7e2Rr4f2b+prdQbrdhDLT+5H9PR4BHUMMhEHT/kPP3iE3Y9u6rbp9ZSm92SlcC3SxSF3VwG0ycx6ObP/LlqXp9aVE3/fm7cAbdjFPrXR5VRV/gLXzG3pqbR20fv1TKf6oG2YeB4pifctFS1v29+iMPBq8s7a6zqYE7u/xRGhv4kBoYn0HTUl1Gqg79iSvmqYUizz0l2dBuxJr1qdbhCb6G0TY8WLHDnnVtKprz9heauW50E7EgdBL+3tMYWdv93r7jfWlvh86jBgIg5b6o9bwFaM6skXqPOg94kBoqb8SLO+I/37dv8qzsfEPW/rLGdB+xEAYtKI/e6TueMsdvLmD5f09XPWXDegSAr1JjJ440NKW9R066vcJBvDXiWlL+LD9PTagsz+0C/GAHmVpdn0YKneTBuT2R/SjWtxVsUguz74+/9OhTcRpdgPQQtcdaMX+HhT1FPmOXzaEAWzt7/FQ6GVicMTQzVxWoNX6y9vZdXcAckiLqmJVLMy5tz7dc6FdiQOhJf2Fj50w4N9toNl1Qi8creovz4IOINbkTxehjft7tMgttfQoWmcD8Nzf43HQ3sSB0Pb9PTr3Bxq8GpJ5wft7oOEuwkcVabWEj+zvYSEGwqDt+3uoCt4nUdNCes81FUlxJw7ac6sMN+INS394f4/xVu7X3feb8Njf49ugHYmHRz4KmvYfplF9za+LiwB6P43nQXsQB0LT8Ueazb3QqM6F9tP3R7EqFoksPvt7HAPtSgyEQQ/6S6IDawx3MI1P6+8lB1C5VnRNS4Z6/pIGm8dAF2l5N/X8xWTmOpu/NMRAmKXv+ktqTjpTd1X3bflsC95SX+QGAlS9/dL+HiN0u9vwB6HV+Q+rmTEIRXdiIMzSs++XdEcGbfDW2mnAr6oNocWuHV3+9DBoV2IgDLrZ3yMRRxD4vDDQPIoLvGr95TDoGGIgDJr2H9NUMO7UU/y/kdPvR5HZXUKT9TX6j2Ogd4jfbuP68yFKVvv+6vP4Aw1+uiNjoL6avOrVPBQXuWlzYS/CVv9xGPQ+cTtQUu8/nKFn/S+JjrDe7Bhg093azaNYFdn0vfqxw6C9iYEwaKL+tJ0A1iETk5oKfAy1ke9RnQSPptmvxlJ/ehi0HzEQBq2oX+ez/lRtdf1t6r0k+rIEcxj0HjEQBv3qfzHdStK+TYx2/eUw6DBiq/H2+18qi8zfBCamhg4Tu/1zwdCmO/EjnkJjPj6cLU3UJ6cBvxJG7tjRN2nwKWS+TFcRnJ4KbSVOAnEU9FA/Np17TUdyndXb3++AArSeqPWXM6C9iYEw6Hv9aWKRp+LBPUzqqEGk+Hd+pPNMZ0DHEEdB0+snd7lfPq9NpH/55K3+aAnpToJ2InbBnUOnf3Vad5r9uHyRSB5vXhwE+pOgY4ijoGf9L2nwgxoXOWtVrAT78okiBDkD2pXYkbUHSf+kipO8zof43OX8aOgY4hDo9A8ef+9KVcz41678F0AnzytR0OnSxFHJ6Xd0oZvTavfnQfsRf9bS/3/9z77+A2DdeCv3ceV0AAAAAElFTkSuQmCC')",
      };

      p.padM.style.backgroundImage = bgs['cross'];
      p.padM.style.backgroundRepeat = 'no-repeat';
      p.sldM.style.backgroundImage = bgs['arrow'];
      p.sldM.style.backgroundRepeat = 'no-repeat';
      p.pad.style.backgroundImage = bgs[padImg];
      p.pad.style.backgroundRepeat = 'no-repeat';
      p.pad.style.backgroundPosition = '0 0';

      // place pointers
      redrawPad();
      redrawSld();

      mxJSColor.picker.owner = THIS;
      document.getElementsByTagName('body')[0].appendChild(p.boxB);
    }

    function getPickerDims(o) {
      var dims = [
        2 * o.pickerInset +
          2 * o.pickerFace +
          mxJSColor.images.pad[0] +
          (o.slider ? 2 * o.pickerInset + 2 * mxJSColor.images.arrow[0] + mxJSColor.images.sld[0] : 0),
        o.pickerClosable
          ? 4 * o.pickerInset + 3 * o.pickerFace + mxJSColor.images.pad[1] + o.pickerButtonHeight
          : 2 * o.pickerInset + 2 * o.pickerFace + mxJSColor.images.pad[1],
      ];
      return dims;
    }

    function redrawPad() {
      // redraw the pad pointer
      switch (modeID) {
        case 0:
          var yComponent = 1;
          break;
        case 1:
          var yComponent = 2;
          break;
      }
      var x = Math.round((THIS.hsv[0] / 6) * (mxJSColor.images.pad[0] - 1));
      var y = Math.round((1 - THIS.hsv[yComponent]) * (mxJSColor.images.pad[1] - 1));
      mxJSColor.picker.padM.style.backgroundPosition =
        THIS.pickerFace +
        THIS.pickerInset +
        x -
        Math.floor(mxJSColor.images.cross[0] / 2) +
        'px ' +
        (THIS.pickerFace + THIS.pickerInset + y - Math.floor(mxJSColor.images.cross[1] / 2)) +
        'px';

      // redraw the slider image
      var seg = mxJSColor.picker.sld.childNodes;

      switch (modeID) {
        case 0:
          var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 1);
          for (var i = 0; i < seg.length; i += 1) {
            seg[i].style.backgroundColor =
              'rgb(' +
              rgb[0] * (1 - i / seg.length) * 100 +
              '%,' +
              rgb[1] * (1 - i / seg.length) * 100 +
              '%,' +
              rgb[2] * (1 - i / seg.length) * 100 +
              '%)';
          }
          break;
        case 1:
          var rgb,
            s,
            c = [THIS.hsv[2], 0, 0];
          var i = Math.floor(THIS.hsv[0]);
          var f = i % 2 ? THIS.hsv[0] - i : 1 - (THIS.hsv[0] - i);
          switch (i) {
            case 6:
            case 0:
              rgb = [0, 1, 2];
              break;
            case 1:
              rgb = [1, 0, 2];
              break;
            case 2:
              rgb = [2, 0, 1];
              break;
            case 3:
              rgb = [2, 1, 0];
              break;
            case 4:
              rgb = [1, 2, 0];
              break;
            case 5:
              rgb = [0, 2, 1];
              break;
          }
          for (var i = 0; i < seg.length; i += 1) {
            s = 1 - (1 / (seg.length - 1)) * i;
            c[1] = c[0] * (1 - s * f);
            c[2] = c[0] * (1 - s);
            seg[i].style.backgroundColor =
              'rgb(' + c[rgb[0]] * 100 + '%,' + c[rgb[1]] * 100 + '%,' + c[rgb[2]] * 100 + '%)';
          }
          break;
      }
    }

    function redrawSld() {
      // redraw the slider pointer
      switch (modeID) {
        case 0:
          var yComponent = 2;
          break;
        case 1:
          var yComponent = 1;
          break;
      }
      var y = Math.round((1 - THIS.hsv[yComponent]) * (mxJSColor.images.sld[1] - 1));
      mxJSColor.picker.sldM.style.backgroundPosition =
        '0 ' + (THIS.pickerFace + THIS.pickerInset + y - Math.floor(mxJSColor.images.arrow[1] / 2)) + 'px';
    }

    function isPickerOwner() {
      return mxJSColor.picker && mxJSColor.picker.owner === THIS;
    }

    function blurTarget() {
      if (valueElement === target) {
        THIS.importColor();
      }
      if (THIS.pickerOnfocus) {
        THIS.hidePicker();
      }
    }

    function blurValue() {
      if (valueElement !== target) {
        THIS.importColor();
      }
    }

    function setPad(e) {
      var mpos = mxJSColor.getRelMousePos(e);
      var x = mpos.x - THIS.pickerFace - THIS.pickerInset;
      var y = mpos.y - THIS.pickerFace - THIS.pickerInset;
      switch (modeID) {
        case 0:
          THIS.fromHSV(x * (6 / (mxJSColor.images.pad[0] - 1)), 1 - y / (mxJSColor.images.pad[1] - 1), null, leaveSld);
          break;
        case 1:
          THIS.fromHSV(x * (6 / (mxJSColor.images.pad[0] - 1)), null, 1 - y / (mxJSColor.images.pad[1] - 1), leaveSld);
          break;
      }
    }

    function setSld(e) {
      var mpos = mxJSColor.getRelMousePos(e);
      var y = mpos.y - THIS.pickerFace - THIS.pickerInset;
      switch (modeID) {
        case 0:
          THIS.fromHSV(null, null, 1 - y / (mxJSColor.images.sld[1] - 1), leavePad);
          break;
        case 1:
          THIS.fromHSV(null, 1 - y / (mxJSColor.images.sld[1] - 1), null, leavePad);
          break;
      }
    }

    function dispatchImmediateChange() {
      if (THIS.onImmediateChange) {
        if (typeof THIS.onImmediateChange === 'string') {
          eval(THIS.onImmediateChange);
        } else {
          THIS.onImmediateChange(THIS);
        }
      }
    }

    var THIS = this;
    var modeID = this.pickerMode.toLowerCase() === 'hvs' ? 1 : 0;
    var abortBlur = false;
    var valueElement = mxJSColor.fetchElement(this.valueElement),
      styleElement = mxJSColor.fetchElement(this.styleElement);
    var holdPad = false,
      holdSld = false;
    var leaveValue = 1 << 0,
      leaveStyle = 1 << 1,
      leavePad = 1 << 2,
      leaveSld = 1 << 3;

    // target
    /*mxJSColor.addEvent(target, 'focus', function() {
			if(THIS.pickerOnfocus) { THIS.showPicker(); }
		});
		mxJSColor.addEvent(target, 'blur', function() {
			if(!abortBlur) {
				window.setTimeout(function(){ abortBlur || blurTarget(); abortBlur=false; }, 0);
			} else {
				abortBlur = false;
			}
		});*/

    // valueElement
    if (valueElement) {
      var updateField = function () {
        THIS.fromString(valueElement.value, leaveValue);
        dispatchImmediateChange();
      };
      mxJSColor.addEvent(valueElement, 'keyup', updateField);
      mxJSColor.addEvent(valueElement, 'input', updateField);
      mxJSColor.addEvent(valueElement, 'blur', blurValue);
      valueElement.setAttribute('autocomplete', 'off');
    }

    // styleElement
    if (styleElement) {
      styleElement.jscStyle = {
        backgroundImage: styleElement.style.backgroundImage,
        backgroundColor: styleElement.style.backgroundColor,
        color: styleElement.style.color,
      };
    }

    // require images
    switch (modeID) {
      case 0:
        mxJSColor.requireImage('hs.png');
        break;
      case 1:
        mxJSColor.requireImage('hv.png');
        break;
    }
    mxJSColor.requireImage('cross.gif');
    mxJSColor.requireImage('arrow.gif');

    this.importColor();
  },
};

mxJSColor.install();

// Sets the base path, the UI language via URL param and configures the
// supported languages to avoid 404s. The loading of all core language
// resources is disabled as all required resources are in grapheditor.
// properties. Note that in this example the loading of two resource
// files (the special bundle and the default bundle) is disabled to
// save a GET request. This requires that all resources be present in
// each properties file since only one file is loaded.
window.mxBasePath = window.mxBasePath || '/';
window.mxLanguage = window.mxLanguage || urlParams['lang'];
window.mxLanguages = window.mxLanguages || ['de', 'se'];
!(function (t) {
  if ('object' == typeof exports && 'undefined' != typeof module) module.exports = t();
  else if ('function' == typeof define && define.amd) define([], t);
  else {
    ('undefined' != typeof window
      ? window
      : 'undefined' != typeof global
      ? global
      : 'undefined' != typeof self
      ? self
      : this
    ).pako = t();
  }
})(function () {
  return (function r(s, o, l) {
    function h(e, t) {
      if (!o[e]) {
        if (!s[e]) {
          var a = 'function' == typeof require && require;
          if (!t && a) return a(e, !0);
          if (d) return d(e, !0);
          var i = new Error("Cannot find module '" + e + "'");
          throw ((i.code = 'MODULE_NOT_FOUND'), i);
        }
        var n = (o[e] = { exports: {} });
        s[e][0].call(
          n.exports,
          function (t) {
            return h(s[e][1][t] || t);
          },
          n,
          n.exports,
          r,
          s,
          o,
          l
        );
      }
      return o[e].exports;
    }
    for (var d = 'function' == typeof require && require, t = 0; t < l.length; t++) h(l[t]);
    return h;
  })(
    {
      '1': [
        function (t, e, a) {
          'use strict';
          var s = t('./zlib/deflate'),
            o = t('./utils/common'),
            l = t('./utils/strings'),
            n = t('./zlib/messages'),
            r = t('./zlib/zstream'),
            h = Object.prototype.toString,
            d = 0,
            f = -1,
            _ = 0,
            u = 8;
          function c(t) {
            if (!(this instanceof c)) return new c(t);
            this.options = o.assign(
              { level: f, method: u, chunkSize: 16384, windowBits: 15, memLevel: 8, strategy: _, to: '' },
              t || {}
            );
            var e = this.options;
            e.raw && 0 < e.windowBits
              ? (e.windowBits = -e.windowBits)
              : e.gzip && 0 < e.windowBits && e.windowBits < 16 && (e.windowBits += 16),
              (this.err = 0),
              (this.msg = ''),
              (this.ended = !1),
              (this.chunks = []),
              (this.strm = new r()),
              (this.strm.avail_out = 0);
            var a = s.deflateInit2(this.strm, e.level, e.method, e.windowBits, e.memLevel, e.strategy);
            if (a !== d) throw new Error(n[a]);
            if ((e.header && s.deflateSetHeader(this.strm, e.header), e.dictionary)) {
              var i;
              if (
                ((i =
                  'string' == typeof e.dictionary
                    ? l.string2buf(e.dictionary)
                    : '[object ArrayBuffer]' === h.call(e.dictionary)
                    ? new Uint8Array(e.dictionary)
                    : e.dictionary),
                (a = s.deflateSetDictionary(this.strm, i)) !== d)
              )
                throw new Error(n[a]);
              this._dict_set = !0;
            }
          }
          function i(t, e) {
            var a = new c(e);
            if ((a.push(t, !0), a.err)) throw a.msg || n[a.err];
            return a.result;
          }
          (c.prototype.push = function (t, e) {
            var a,
              i,
              n = this.strm,
              r = this.options.chunkSize;
            if (this.ended) return !1;
            (i = e === ~~e ? e : !0 === e ? 4 : 0),
              'string' == typeof t
                ? (n.input = l.string2buf(t))
                : '[object ArrayBuffer]' === h.call(t)
                ? (n.input = new Uint8Array(t))
                : (n.input = t),
              (n.next_in = 0),
              (n.avail_in = n.input.length);
            do {
              if (
                (0 === n.avail_out && ((n.output = new o.Buf8(r)), (n.next_out = 0), (n.avail_out = r)),
                1 !== (a = s.deflate(n, i)) && a !== d)
              )
                return this.onEnd(a), !(this.ended = !0);
              (0 !== n.avail_out && (0 !== n.avail_in || (4 !== i && 2 !== i))) ||
                ('string' === this.options.to
                  ? this.onData(l.buf2binstring(o.shrinkBuf(n.output, n.next_out)))
                  : this.onData(o.shrinkBuf(n.output, n.next_out)));
            } while ((0 < n.avail_in || 0 === n.avail_out) && 1 !== a);
            return 4 === i
              ? ((a = s.deflateEnd(this.strm)), this.onEnd(a), (this.ended = !0), a === d)
              : 2 !== i || (this.onEnd(d), !(n.avail_out = 0));
          }),
            (c.prototype.onData = function (t) {
              this.chunks.push(t);
            }),
            (c.prototype.onEnd = function (t) {
              t === d &&
                ('string' === this.options.to
                  ? (this.result = this.chunks.join(''))
                  : (this.result = o.flattenChunks(this.chunks))),
                (this.chunks = []),
                (this.err = t),
                (this.msg = this.strm.msg);
            }),
            (a.Deflate = c),
            (a.deflate = i),
            (a.deflateRaw = function (t, e) {
              return ((e = e || {}).raw = !0), i(t, e);
            }),
            (a.gzip = function (t, e) {
              return ((e = e || {}).gzip = !0), i(t, e);
            });
        },
        { './utils/common': 3, './utils/strings': 4, './zlib/deflate': 8, './zlib/messages': 13, './zlib/zstream': 15 },
      ],
      '2': [
        function (t, e, a) {
          'use strict';
          var f = t('./zlib/inflate'),
            _ = t('./utils/common'),
            u = t('./utils/strings'),
            c = t('./zlib/constants'),
            i = t('./zlib/messages'),
            n = t('./zlib/zstream'),
            r = t('./zlib/gzheader'),
            b = Object.prototype.toString;
          function s(t) {
            if (!(this instanceof s)) return new s(t);
            this.options = _.assign({ chunkSize: 16384, windowBits: 0, to: '' }, t || {});
            var e = this.options;
            e.raw &&
              0 <= e.windowBits &&
              e.windowBits < 16 &&
              ((e.windowBits = -e.windowBits), 0 === e.windowBits && (e.windowBits = -15)),
              !(0 <= e.windowBits && e.windowBits < 16) || (t && t.windowBits) || (e.windowBits += 32),
              15 < e.windowBits && e.windowBits < 48 && 0 == (15 & e.windowBits) && (e.windowBits |= 15),
              (this.err = 0),
              (this.msg = ''),
              (this.ended = !1),
              (this.chunks = []),
              (this.strm = new n()),
              (this.strm.avail_out = 0);
            var a = f.inflateInit2(this.strm, e.windowBits);
            if (a !== c.Z_OK) throw new Error(i[a]);
            if (
              ((this.header = new r()),
              f.inflateGetHeader(this.strm, this.header),
              e.dictionary &&
                ('string' == typeof e.dictionary
                  ? (e.dictionary = u.string2buf(e.dictionary))
                  : '[object ArrayBuffer]' === b.call(e.dictionary) && (e.dictionary = new Uint8Array(e.dictionary)),
                e.raw && (a = f.inflateSetDictionary(this.strm, e.dictionary)) !== c.Z_OK))
            )
              throw new Error(i[a]);
          }
          function o(t, e) {
            var a = new s(e);
            if ((a.push(t, !0), a.err)) throw a.msg || i[a.err];
            return a.result;
          }
          (s.prototype.push = function (t, e) {
            var a,
              i,
              n,
              r,
              s,
              o = this.strm,
              l = this.options.chunkSize,
              h = this.options.dictionary,
              d = !1;
            if (this.ended) return !1;
            (i = e === ~~e ? e : !0 === e ? c.Z_FINISH : c.Z_NO_FLUSH),
              'string' == typeof t
                ? (o.input = u.binstring2buf(t))
                : '[object ArrayBuffer]' === b.call(t)
                ? (o.input = new Uint8Array(t))
                : (o.input = t),
              (o.next_in = 0),
              (o.avail_in = o.input.length);
            do {
              if (
                (0 === o.avail_out && ((o.output = new _.Buf8(l)), (o.next_out = 0), (o.avail_out = l)),
                (a = f.inflate(o, c.Z_NO_FLUSH)) === c.Z_NEED_DICT && h && (a = f.inflateSetDictionary(this.strm, h)),
                a === c.Z_BUF_ERROR && !0 === d && ((a = c.Z_OK), (d = !1)),
                a !== c.Z_STREAM_END && a !== c.Z_OK)
              )
                return this.onEnd(a), !(this.ended = !0);
              o.next_out &&
                ((0 !== o.avail_out &&
                  a !== c.Z_STREAM_END &&
                  (0 !== o.avail_in || (i !== c.Z_FINISH && i !== c.Z_SYNC_FLUSH))) ||
                  ('string' === this.options.to
                    ? ((n = u.utf8border(o.output, o.next_out)),
                      (r = o.next_out - n),
                      (s = u.buf2string(o.output, n)),
                      (o.next_out = r),
                      (o.avail_out = l - r),
                      r && _.arraySet(o.output, o.output, n, r, 0),
                      this.onData(s))
                    : this.onData(_.shrinkBuf(o.output, o.next_out)))),
                0 === o.avail_in && 0 === o.avail_out && (d = !0);
            } while ((0 < o.avail_in || 0 === o.avail_out) && a !== c.Z_STREAM_END);
            return (
              a === c.Z_STREAM_END && (i = c.Z_FINISH),
              i === c.Z_FINISH
                ? ((a = f.inflateEnd(this.strm)), this.onEnd(a), (this.ended = !0), a === c.Z_OK)
                : i !== c.Z_SYNC_FLUSH || (this.onEnd(c.Z_OK), !(o.avail_out = 0))
            );
          }),
            (s.prototype.onData = function (t) {
              this.chunks.push(t);
            }),
            (s.prototype.onEnd = function (t) {
              t === c.Z_OK &&
                ('string' === this.options.to
                  ? (this.result = this.chunks.join(''))
                  : (this.result = _.flattenChunks(this.chunks))),
                (this.chunks = []),
                (this.err = t),
                (this.msg = this.strm.msg);
            }),
            (a.Inflate = s),
            (a.inflate = o),
            (a.inflateRaw = function (t, e) {
              return ((e = e || {}).raw = !0), o(t, e);
            }),
            (a.ungzip = o);
        },
        {
          './utils/common': 3,
          './utils/strings': 4,
          './zlib/constants': 6,
          './zlib/gzheader': 9,
          './zlib/inflate': 11,
          './zlib/messages': 13,
          './zlib/zstream': 15,
        },
      ],
      '3': [
        function (t, e, a) {
          'use strict';
          var i =
            'undefined' != typeof Uint8Array && 'undefined' != typeof Uint16Array && 'undefined' != typeof Int32Array;
          (a.assign = function (t) {
            for (var e, a, i = Array.prototype.slice.call(arguments, 1); i.length; ) {
              var n = i.shift();
              if (n) {
                if ('object' != typeof n) throw new TypeError(n + 'must be non-object');
                for (var r in n) (e = n), (a = r), Object.prototype.hasOwnProperty.call(e, a) && (t[r] = n[r]);
              }
            }
            return t;
          }),
            (a.shrinkBuf = function (t, e) {
              return t.length === e ? t : t.subarray ? t.subarray(0, e) : ((t.length = e), t);
            });
          var n = {
              arraySet: function (t, e, a, i, n) {
                if (e.subarray && t.subarray) t.set(e.subarray(a, a + i), n);
                else for (var r = 0; r < i; r++) t[n + r] = e[a + r];
              },
              flattenChunks: function (t) {
                var e, a, i, n, r, s;
                for (e = i = 0, a = t.length; e < a; e++) i += t[e].length;
                for (s = new Uint8Array(i), e = n = 0, a = t.length; e < a; e++)
                  (r = t[e]), s.set(r, n), (n += r.length);
                return s;
              },
            },
            r = {
              arraySet: function (t, e, a, i, n) {
                for (var r = 0; r < i; r++) t[n + r] = e[a + r];
              },
              flattenChunks: function (t) {
                return [].concat.apply([], t);
              },
            };
          (a.setTyped = function (t) {
            t
              ? ((a.Buf8 = Uint8Array), (a.Buf16 = Uint16Array), (a.Buf32 = Int32Array), a.assign(a, n))
              : ((a.Buf8 = Array), (a.Buf16 = Array), (a.Buf32 = Array), a.assign(a, r));
          }),
            a.setTyped(i);
        },
        {},
      ],
      '4': [
        function (t, e, a) {
          'use strict';
          var l = t('./common'),
            n = !0,
            r = !0;
          try {
            String.fromCharCode.apply(null, [0]);
          } catch (t) {
            n = !1;
          }
          try {
            String.fromCharCode.apply(null, new Uint8Array(1));
          } catch (t) {
            r = !1;
          }
          for (var h = new l.Buf8(256), i = 0; i < 256; i++)
            h[i] = 252 <= i ? 6 : 248 <= i ? 5 : 240 <= i ? 4 : 224 <= i ? 3 : 192 <= i ? 2 : 1;
          function d(t, e) {
            if (e < 65534 && ((t.subarray && r) || (!t.subarray && n)))
              return String.fromCharCode.apply(null, l.shrinkBuf(t, e));
            for (var a = '', i = 0; i < e; i++) a += String.fromCharCode(t[i]);
            return a;
          }
          (h[254] = h[254] = 1),
            (a.string2buf = function (t) {
              var e,
                a,
                i,
                n,
                r,
                s = t.length,
                o = 0;
              for (n = 0; n < s; n++)
                55296 == (64512 & (a = t.charCodeAt(n))) &&
                  n + 1 < s &&
                  56320 == (64512 & (i = t.charCodeAt(n + 1))) &&
                  ((a = 65536 + ((a - 55296) << 10) + (i - 56320)), n++),
                  (o += a < 128 ? 1 : a < 2048 ? 2 : a < 65536 ? 3 : 4);
              for (e = new l.Buf8(o), n = r = 0; r < o; n++)
                55296 == (64512 & (a = t.charCodeAt(n))) &&
                  n + 1 < s &&
                  56320 == (64512 & (i = t.charCodeAt(n + 1))) &&
                  ((a = 65536 + ((a - 55296) << 10) + (i - 56320)), n++),
                  a < 128
                    ? (e[r++] = a)
                    : (a < 2048
                        ? (e[r++] = 192 | (a >>> 6))
                        : (a < 65536
                            ? (e[r++] = 224 | (a >>> 12))
                            : ((e[r++] = 240 | (a >>> 18)), (e[r++] = 128 | ((a >>> 12) & 63))),
                          (e[r++] = 128 | ((a >>> 6) & 63))),
                      (e[r++] = 128 | (63 & a)));
              return e;
            }),
            (a.buf2binstring = function (t) {
              return d(t, t.length);
            }),
            (a.binstring2buf = function (t) {
              for (var e = new l.Buf8(t.length), a = 0, i = e.length; a < i; a++) e[a] = t.charCodeAt(a);
              return e;
            }),
            (a.buf2string = function (t, e) {
              var a,
                i,
                n,
                r,
                s = e || t.length,
                o = new Array(2 * s);
              for (a = i = 0; a < s; )
                if ((n = t[a++]) < 128) o[i++] = n;
                else if (4 < (r = h[n])) (o[i++] = 65533), (a += r - 1);
                else {
                  for (n &= 2 === r ? 31 : 3 === r ? 15 : 7; 1 < r && a < s; ) (n = (n << 6) | (63 & t[a++])), r--;
                  1 < r
                    ? (o[i++] = 65533)
                    : n < 65536
                    ? (o[i++] = n)
                    : ((n -= 65536), (o[i++] = 55296 | ((n >> 10) & 1023)), (o[i++] = 56320 | (1023 & n)));
                }
              return d(o, i);
            }),
            (a.utf8border = function (t, e) {
              var a;
              for ((e = e || t.length) > t.length && (e = t.length), a = e - 1; 0 <= a && 128 == (192 & t[a]); ) a--;
              return a < 0 ? e : 0 === a ? e : a + h[t[a]] > e ? a : e;
            });
        },
        { './common': 3 },
      ],
      '5': [
        function (t, e, a) {
          'use strict';
          e.exports = function (t, e, a, i) {
            for (var n = (65535 & t) | 0, r = ((t >>> 16) & 65535) | 0, s = 0; 0 !== a; ) {
              for (a -= s = 2e3 < a ? 2e3 : a; (r = (r + (n = (n + e[i++]) | 0)) | 0), --s; );
              (n %= 65521), (r %= 65521);
            }
            return n | (r << 16) | 0;
          };
        },
        {},
      ],
      '6': [
        function (t, e, a) {
          'use strict';
          e.exports = {
            Z_NO_FLUSH: 0,
            Z_PARTIAL_FLUSH: 1,
            Z_SYNC_FLUSH: 2,
            Z_FULL_FLUSH: 3,
            Z_FINISH: 4,
            Z_BLOCK: 5,
            Z_TREES: 6,
            Z_OK: 0,
            Z_STREAM_END: 1,
            Z_NEED_DICT: 2,
            Z_ERRNO: -1,
            Z_STREAM_ERROR: -2,
            Z_DATA_ERROR: -3,
            Z_BUF_ERROR: -5,
            Z_NO_COMPRESSION: 0,
            Z_BEST_SPEED: 1,
            Z_BEST_COMPRESSION: 9,
            Z_DEFAULT_COMPRESSION: -1,
            Z_FILTERED: 1,
            Z_HUFFMAN_ONLY: 2,
            Z_RLE: 3,
            Z_FIXED: 4,
            Z_DEFAULT_STRATEGY: 0,
            Z_BINARY: 0,
            Z_TEXT: 1,
            Z_UNKNOWN: 2,
            Z_DEFLATED: 8,
          };
        },
        {},
      ],
      '7': [
        function (t, e, a) {
          'use strict';
          var o = (function () {
            for (var t, e = [], a = 0; a < 256; a++) {
              t = a;
              for (var i = 0; i < 8; i++) t = 1 & t ? 3988292384 ^ (t >>> 1) : t >>> 1;
              e[a] = t;
            }
            return e;
          })();
          e.exports = function (t, e, a, i) {
            var n = o,
              r = i + a;
            t ^= -1;
            for (var s = i; s < r; s++) t = (t >>> 8) ^ n[255 & (t ^ e[s])];
            return -1 ^ t;
          };
        },
        {},
      ],
      '8': [
        function (t, e, a) {
          'use strict';
          var l,
            _ = t('../utils/common'),
            h = t('./trees'),
            u = t('./adler32'),
            c = t('./crc32'),
            i = t('./messages'),
            d = 0,
            f = 4,
            b = 0,
            g = -2,
            m = -1,
            w = 4,
            n = 2,
            p = 8,
            v = 9,
            r = 286,
            s = 30,
            o = 19,
            k = 2 * r + 1,
            y = 15,
            x = 3,
            z = 258,
            B = z + x + 1,
            S = 42,
            E = 113,
            A = 1,
            Z = 2,
            R = 3,
            C = 4;
          function N(t, e) {
            return (t.msg = i[e]), e;
          }
          function O(t) {
            return (t << 1) - (4 < t ? 9 : 0);
          }
          function D(t) {
            for (var e = t.length; 0 <= --e; ) t[e] = 0;
          }
          function I(t) {
            var e = t.state,
              a = e.pending;
            a > t.avail_out && (a = t.avail_out),
              0 !== a &&
                (_.arraySet(t.output, e.pending_buf, e.pending_out, a, t.next_out),
                (t.next_out += a),
                (e.pending_out += a),
                (t.total_out += a),
                (t.avail_out -= a),
                (e.pending -= a),
                0 === e.pending && (e.pending_out = 0));
          }
          function U(t, e) {
            h._tr_flush_block(t, 0 <= t.block_start ? t.block_start : -1, t.strstart - t.block_start, e),
              (t.block_start = t.strstart),
              I(t.strm);
          }
          function T(t, e) {
            t.pending_buf[t.pending++] = e;
          }
          function F(t, e) {
            (t.pending_buf[t.pending++] = (e >>> 8) & 255), (t.pending_buf[t.pending++] = 255 & e);
          }
          function L(t, e) {
            var a,
              i,
              n = t.max_chain_length,
              r = t.strstart,
              s = t.prev_length,
              o = t.nice_match,
              l = t.strstart > t.w_size - B ? t.strstart - (t.w_size - B) : 0,
              h = t.window,
              d = t.w_mask,
              f = t.prev,
              _ = t.strstart + z,
              u = h[r + s - 1],
              c = h[r + s];
            t.prev_length >= t.good_match && (n >>= 2), o > t.lookahead && (o = t.lookahead);
            do {
              if (h[(a = e) + s] === c && h[a + s - 1] === u && h[a] === h[r] && h[++a] === h[r + 1]) {
                (r += 2), a++;
                do {} while (
                  h[++r] === h[++a] &&
                  h[++r] === h[++a] &&
                  h[++r] === h[++a] &&
                  h[++r] === h[++a] &&
                  h[++r] === h[++a] &&
                  h[++r] === h[++a] &&
                  h[++r] === h[++a] &&
                  h[++r] === h[++a] &&
                  r < _
                );
                if (((i = z - (_ - r)), (r = _ - z), s < i)) {
                  if (((t.match_start = e), o <= (s = i))) break;
                  (u = h[r + s - 1]), (c = h[r + s]);
                }
              }
            } while ((e = f[e & d]) > l && 0 != --n);
            return s <= t.lookahead ? s : t.lookahead;
          }
          function H(t) {
            var e,
              a,
              i,
              n,
              r,
              s,
              o,
              l,
              h,
              d,
              f = t.w_size;
            do {
              if (((n = t.window_size - t.lookahead - t.strstart), t.strstart >= f + (f - B))) {
                for (
                  _.arraySet(t.window, t.window, f, f, 0),
                    t.match_start -= f,
                    t.strstart -= f,
                    t.block_start -= f,
                    e = a = t.hash_size;
                  (i = t.head[--e]), (t.head[e] = f <= i ? i - f : 0), --a;

                );
                for (e = a = f; (i = t.prev[--e]), (t.prev[e] = f <= i ? i - f : 0), --a; );
                n += f;
              }
              if (0 === t.strm.avail_in) break;
              if (
                ((s = t.strm),
                (o = t.window),
                (l = t.strstart + t.lookahead),
                (h = n),
                (d = void 0),
                (d = s.avail_in),
                h < d && (d = h),
                (a =
                  0 === d
                    ? 0
                    : ((s.avail_in -= d),
                      _.arraySet(o, s.input, s.next_in, d, l),
                      1 === s.state.wrap
                        ? (s.adler = u(s.adler, o, d, l))
                        : 2 === s.state.wrap && (s.adler = c(s.adler, o, d, l)),
                      (s.next_in += d),
                      (s.total_in += d),
                      d)),
                (t.lookahead += a),
                t.lookahead + t.insert >= x)
              )
                for (
                  r = t.strstart - t.insert,
                    t.ins_h = t.window[r],
                    t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[r + 1]) & t.hash_mask;
                  t.insert &&
                  ((t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[r + x - 1]) & t.hash_mask),
                  (t.prev[r & t.w_mask] = t.head[t.ins_h]),
                  (t.head[t.ins_h] = r),
                  r++,
                  t.insert--,
                  !(t.lookahead + t.insert < x));

                );
            } while (t.lookahead < B && 0 !== t.strm.avail_in);
          }
          function j(t, e) {
            for (var a, i; ; ) {
              if (t.lookahead < B) {
                if ((H(t), t.lookahead < B && e === d)) return A;
                if (0 === t.lookahead) break;
              }
              if (
                ((a = 0),
                t.lookahead >= x &&
                  ((t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[t.strstart + x - 1]) & t.hash_mask),
                  (a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h]),
                  (t.head[t.ins_h] = t.strstart)),
                0 !== a && t.strstart - a <= t.w_size - B && (t.match_length = L(t, a)),
                t.match_length >= x)
              )
                if (
                  ((i = h._tr_tally(t, t.strstart - t.match_start, t.match_length - x)),
                  (t.lookahead -= t.match_length),
                  t.match_length <= t.max_lazy_match && t.lookahead >= x)
                ) {
                  for (
                    t.match_length--;
                    t.strstart++,
                      (t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[t.strstart + x - 1]) & t.hash_mask),
                      (a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h]),
                      (t.head[t.ins_h] = t.strstart),
                      0 != --t.match_length;

                  );
                  t.strstart++;
                } else
                  (t.strstart += t.match_length),
                    (t.match_length = 0),
                    (t.ins_h = t.window[t.strstart]),
                    (t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[t.strstart + 1]) & t.hash_mask);
              else (i = h._tr_tally(t, 0, t.window[t.strstart])), t.lookahead--, t.strstart++;
              if (i && (U(t, !1), 0 === t.strm.avail_out)) return A;
            }
            return (
              (t.insert = t.strstart < x - 1 ? t.strstart : x - 1),
              e === f
                ? (U(t, !0), 0 === t.strm.avail_out ? R : C)
                : t.last_lit && (U(t, !1), 0 === t.strm.avail_out)
                ? A
                : Z
            );
          }
          function K(t, e) {
            for (var a, i, n; ; ) {
              if (t.lookahead < B) {
                if ((H(t), t.lookahead < B && e === d)) return A;
                if (0 === t.lookahead) break;
              }
              if (
                ((a = 0),
                t.lookahead >= x &&
                  ((t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[t.strstart + x - 1]) & t.hash_mask),
                  (a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h]),
                  (t.head[t.ins_h] = t.strstart)),
                (t.prev_length = t.match_length),
                (t.prev_match = t.match_start),
                (t.match_length = x - 1),
                0 !== a &&
                  t.prev_length < t.max_lazy_match &&
                  t.strstart - a <= t.w_size - B &&
                  ((t.match_length = L(t, a)),
                  t.match_length <= 5 &&
                    (1 === t.strategy || (t.match_length === x && 4096 < t.strstart - t.match_start)) &&
                    (t.match_length = x - 1)),
                t.prev_length >= x && t.match_length <= t.prev_length)
              ) {
                for (
                  n = t.strstart + t.lookahead - x,
                    i = h._tr_tally(t, t.strstart - 1 - t.prev_match, t.prev_length - x),
                    t.lookahead -= t.prev_length - 1,
                    t.prev_length -= 2;
                  ++t.strstart <= n &&
                    ((t.ins_h = ((t.ins_h << t.hash_shift) ^ t.window[t.strstart + x - 1]) & t.hash_mask),
                    (a = t.prev[t.strstart & t.w_mask] = t.head[t.ins_h]),
                    (t.head[t.ins_h] = t.strstart)),
                    0 != --t.prev_length;

                );
                if (
                  ((t.match_available = 0),
                  (t.match_length = x - 1),
                  t.strstart++,
                  i && (U(t, !1), 0 === t.strm.avail_out))
                )
                  return A;
              } else if (t.match_available) {
                if (
                  ((i = h._tr_tally(t, 0, t.window[t.strstart - 1])) && U(t, !1),
                  t.strstart++,
                  t.lookahead--,
                  0 === t.strm.avail_out)
                )
                  return A;
              } else (t.match_available = 1), t.strstart++, t.lookahead--;
            }
            return (
              t.match_available && ((i = h._tr_tally(t, 0, t.window[t.strstart - 1])), (t.match_available = 0)),
              (t.insert = t.strstart < x - 1 ? t.strstart : x - 1),
              e === f
                ? (U(t, !0), 0 === t.strm.avail_out ? R : C)
                : t.last_lit && (U(t, !1), 0 === t.strm.avail_out)
                ? A
                : Z
            );
          }
          function M(t, e, a, i, n) {
            (this.good_length = t), (this.max_lazy = e), (this.nice_length = a), (this.max_chain = i), (this.func = n);
          }
          function P() {
            (this.strm = null),
              (this.status = 0),
              (this.pending_buf = null),
              (this.pending_buf_size = 0),
              (this.pending_out = 0),
              (this.pending = 0),
              (this.wrap = 0),
              (this.gzhead = null),
              (this.gzindex = 0),
              (this.method = p),
              (this.last_flush = -1),
              (this.w_size = 0),
              (this.w_bits = 0),
              (this.w_mask = 0),
              (this.window = null),
              (this.window_size = 0),
              (this.prev = null),
              (this.head = null),
              (this.ins_h = 0),
              (this.hash_size = 0),
              (this.hash_bits = 0),
              (this.hash_mask = 0),
              (this.hash_shift = 0),
              (this.block_start = 0),
              (this.match_length = 0),
              (this.prev_match = 0),
              (this.match_available = 0),
              (this.strstart = 0),
              (this.match_start = 0),
              (this.lookahead = 0),
              (this.prev_length = 0),
              (this.max_chain_length = 0),
              (this.max_lazy_match = 0),
              (this.level = 0),
              (this.strategy = 0),
              (this.good_match = 0),
              (this.nice_match = 0),
              (this.dyn_ltree = new _.Buf16(2 * k)),
              (this.dyn_dtree = new _.Buf16(2 * (2 * s + 1))),
              (this.bl_tree = new _.Buf16(2 * (2 * o + 1))),
              D(this.dyn_ltree),
              D(this.dyn_dtree),
              D(this.bl_tree),
              (this.l_desc = null),
              (this.d_desc = null),
              (this.bl_desc = null),
              (this.bl_count = new _.Buf16(y + 1)),
              (this.heap = new _.Buf16(2 * r + 1)),
              D(this.heap),
              (this.heap_len = 0),
              (this.heap_max = 0),
              (this.depth = new _.Buf16(2 * r + 1)),
              D(this.depth),
              (this.l_buf = 0),
              (this.lit_bufsize = 0),
              (this.last_lit = 0),
              (this.d_buf = 0),
              (this.opt_len = 0),
              (this.static_len = 0),
              (this.matches = 0),
              (this.insert = 0),
              (this.bi_buf = 0),
              (this.bi_valid = 0);
          }
          function Y(t) {
            var e;
            return t && t.state
              ? ((t.total_in = t.total_out = 0),
                (t.data_type = n),
                ((e = t.state).pending = 0),
                (e.pending_out = 0),
                e.wrap < 0 && (e.wrap = -e.wrap),
                (e.status = e.wrap ? S : E),
                (t.adler = 2 === e.wrap ? 0 : 1),
                (e.last_flush = d),
                h._tr_init(e),
                b)
              : N(t, g);
          }
          function q(t) {
            var e,
              a = Y(t);
            return (
              a === b &&
                (((e = t.state).window_size = 2 * e.w_size),
                D(e.head),
                (e.max_lazy_match = l[e.level].max_lazy),
                (e.good_match = l[e.level].good_length),
                (e.nice_match = l[e.level].nice_length),
                (e.max_chain_length = l[e.level].max_chain),
                (e.strstart = 0),
                (e.block_start = 0),
                (e.lookahead = 0),
                (e.insert = 0),
                (e.match_length = e.prev_length = x - 1),
                (e.match_available = 0),
                (e.ins_h = 0)),
              a
            );
          }
          function G(t, e, a, i, n, r) {
            if (!t) return g;
            var s = 1;
            if (
              (e === m && (e = 6),
              i < 0 ? ((s = 0), (i = -i)) : 15 < i && ((s = 2), (i -= 16)),
              n < 1 || v < n || a !== p || i < 8 || 15 < i || e < 0 || 9 < e || r < 0 || w < r)
            )
              return N(t, g);
            8 === i && (i = 9);
            var o = new P();
            return (
              ((t.state = o).strm = t),
              (o.wrap = s),
              (o.gzhead = null),
              (o.w_bits = i),
              (o.w_size = 1 << o.w_bits),
              (o.w_mask = o.w_size - 1),
              (o.hash_bits = n + 7),
              (o.hash_size = 1 << o.hash_bits),
              (o.hash_mask = o.hash_size - 1),
              (o.hash_shift = ~~((o.hash_bits + x - 1) / x)),
              (o.window = new _.Buf8(2 * o.w_size)),
              (o.head = new _.Buf16(o.hash_size)),
              (o.prev = new _.Buf16(o.w_size)),
              (o.lit_bufsize = 1 << (n + 6)),
              (o.pending_buf_size = 4 * o.lit_bufsize),
              (o.pending_buf = new _.Buf8(o.pending_buf_size)),
              (o.d_buf = 1 * o.lit_bufsize),
              (o.l_buf = 3 * o.lit_bufsize),
              (o.level = e),
              (o.strategy = r),
              (o.method = a),
              q(t)
            );
          }
          (l = [
            new M(0, 0, 0, 0, function (t, e) {
              var a = 65535;
              for (a > t.pending_buf_size - 5 && (a = t.pending_buf_size - 5); ; ) {
                if (t.lookahead <= 1) {
                  if ((H(t), 0 === t.lookahead && e === d)) return A;
                  if (0 === t.lookahead) break;
                }
                (t.strstart += t.lookahead), (t.lookahead = 0);
                var i = t.block_start + a;
                if (
                  (0 === t.strstart || t.strstart >= i) &&
                  ((t.lookahead = t.strstart - i), (t.strstart = i), U(t, !1), 0 === t.strm.avail_out)
                )
                  return A;
                if (t.strstart - t.block_start >= t.w_size - B && (U(t, !1), 0 === t.strm.avail_out)) return A;
              }
              return (
                (t.insert = 0),
                e === f
                  ? (U(t, !0), 0 === t.strm.avail_out ? R : C)
                  : (t.strstart > t.block_start && (U(t, !1), t.strm.avail_out), A)
              );
            }),
            new M(4, 4, 8, 4, j),
            new M(4, 5, 16, 8, j),
            new M(4, 6, 32, 32, j),
            new M(4, 4, 16, 16, K),
            new M(8, 16, 32, 32, K),
            new M(8, 16, 128, 128, K),
            new M(8, 32, 128, 256, K),
            new M(32, 128, 258, 1024, K),
            new M(32, 258, 258, 4096, K),
          ]),
            (a.deflateInit = function (t, e) {
              return G(t, e, p, 15, 8, 0);
            }),
            (a.deflateInit2 = G),
            (a.deflateReset = q),
            (a.deflateResetKeep = Y),
            (a.deflateSetHeader = function (t, e) {
              return t && t.state ? (2 !== t.state.wrap ? g : ((t.state.gzhead = e), b)) : g;
            }),
            (a.deflate = function (t, e) {
              var a, i, n, r;
              if (!t || !t.state || 5 < e || e < 0) return t ? N(t, g) : g;
              if (((i = t.state), !t.output || (!t.input && 0 !== t.avail_in) || (666 === i.status && e !== f)))
                return N(t, 0 === t.avail_out ? -5 : g);
              if (((i.strm = t), (a = i.last_flush), (i.last_flush = e), i.status === S))
                if (2 === i.wrap)
                  (t.adler = 0),
                    T(i, 31),
                    T(i, 139),
                    T(i, 8),
                    i.gzhead
                      ? (T(
                          i,
                          (i.gzhead.text ? 1 : 0) +
                            (i.gzhead.hcrc ? 2 : 0) +
                            (i.gzhead.extra ? 4 : 0) +
                            (i.gzhead.name ? 8 : 0) +
                            (i.gzhead.comment ? 16 : 0)
                        ),
                        T(i, 255 & i.gzhead.time),
                        T(i, (i.gzhead.time >> 8) & 255),
                        T(i, (i.gzhead.time >> 16) & 255),
                        T(i, (i.gzhead.time >> 24) & 255),
                        T(i, 9 === i.level ? 2 : 2 <= i.strategy || i.level < 2 ? 4 : 0),
                        T(i, 255 & i.gzhead.os),
                        i.gzhead.extra &&
                          i.gzhead.extra.length &&
                          (T(i, 255 & i.gzhead.extra.length), T(i, (i.gzhead.extra.length >> 8) & 255)),
                        i.gzhead.hcrc && (t.adler = c(t.adler, i.pending_buf, i.pending, 0)),
                        (i.gzindex = 0),
                        (i.status = 69))
                      : (T(i, 0),
                        T(i, 0),
                        T(i, 0),
                        T(i, 0),
                        T(i, 0),
                        T(i, 9 === i.level ? 2 : 2 <= i.strategy || i.level < 2 ? 4 : 0),
                        T(i, 3),
                        (i.status = E));
                else {
                  var s = (p + ((i.w_bits - 8) << 4)) << 8;
                  (s |= (2 <= i.strategy || i.level < 2 ? 0 : i.level < 6 ? 1 : 6 === i.level ? 2 : 3) << 6),
                    0 !== i.strstart && (s |= 32),
                    (s += 31 - (s % 31)),
                    (i.status = E),
                    F(i, s),
                    0 !== i.strstart && (F(i, t.adler >>> 16), F(i, 65535 & t.adler)),
                    (t.adler = 1);
                }
              if (69 === i.status)
                if (i.gzhead.extra) {
                  for (
                    n = i.pending;
                    i.gzindex < (65535 & i.gzhead.extra.length) &&
                    (i.pending !== i.pending_buf_size ||
                      (i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)),
                      I(t),
                      (n = i.pending),
                      i.pending !== i.pending_buf_size));

                  )
                    T(i, 255 & i.gzhead.extra[i.gzindex]), i.gzindex++;
                  i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)),
                    i.gzindex === i.gzhead.extra.length && ((i.gzindex = 0), (i.status = 73));
                } else i.status = 73;
              if (73 === i.status)
                if (i.gzhead.name) {
                  n = i.pending;
                  do {
                    if (
                      i.pending === i.pending_buf_size &&
                      (i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)),
                      I(t),
                      (n = i.pending),
                      i.pending === i.pending_buf_size)
                    ) {
                      r = 1;
                      break;
                    }
                    T(i, (r = i.gzindex < i.gzhead.name.length ? 255 & i.gzhead.name.charCodeAt(i.gzindex++) : 0));
                  } while (0 !== r);
                  i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)),
                    0 === r && ((i.gzindex = 0), (i.status = 91));
                } else i.status = 91;
              if (91 === i.status)
                if (i.gzhead.comment) {
                  n = i.pending;
                  do {
                    if (
                      i.pending === i.pending_buf_size &&
                      (i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)),
                      I(t),
                      (n = i.pending),
                      i.pending === i.pending_buf_size)
                    ) {
                      r = 1;
                      break;
                    }
                    T(
                      i,
                      (r = i.gzindex < i.gzhead.comment.length ? 255 & i.gzhead.comment.charCodeAt(i.gzindex++) : 0)
                    );
                  } while (0 !== r);
                  i.gzhead.hcrc && i.pending > n && (t.adler = c(t.adler, i.pending_buf, i.pending - n, n)),
                    0 === r && (i.status = 103);
                } else i.status = 103;
              if (
                (103 === i.status &&
                  (i.gzhead.hcrc
                    ? (i.pending + 2 > i.pending_buf_size && I(t),
                      i.pending + 2 <= i.pending_buf_size &&
                        (T(i, 255 & t.adler), T(i, (t.adler >> 8) & 255), (t.adler = 0), (i.status = E)))
                    : (i.status = E)),
                0 !== i.pending)
              ) {
                if ((I(t), 0 === t.avail_out)) return (i.last_flush = -1), b;
              } else if (0 === t.avail_in && O(e) <= O(a) && e !== f) return N(t, -5);
              if (666 === i.status && 0 !== t.avail_in) return N(t, -5);
              if (0 !== t.avail_in || 0 !== i.lookahead || (e !== d && 666 !== i.status)) {
                var o =
                  2 === i.strategy
                    ? (function (t, e) {
                        for (var a; ; ) {
                          if (0 === t.lookahead && (H(t), 0 === t.lookahead)) {
                            if (e === d) return A;
                            break;
                          }
                          if (
                            ((t.match_length = 0),
                            (a = h._tr_tally(t, 0, t.window[t.strstart])),
                            t.lookahead--,
                            t.strstart++,
                            a && (U(t, !1), 0 === t.strm.avail_out))
                          )
                            return A;
                        }
                        return (
                          (t.insert = 0),
                          e === f
                            ? (U(t, !0), 0 === t.strm.avail_out ? R : C)
                            : t.last_lit && (U(t, !1), 0 === t.strm.avail_out)
                            ? A
                            : Z
                        );
                      })(i, e)
                    : 3 === i.strategy
                    ? (function (t, e) {
                        for (var a, i, n, r, s = t.window; ; ) {
                          if (t.lookahead <= z) {
                            if ((H(t), t.lookahead <= z && e === d)) return A;
                            if (0 === t.lookahead) break;
                          }
                          if (
                            ((t.match_length = 0),
                            t.lookahead >= x &&
                              0 < t.strstart &&
                              (i = s[(n = t.strstart - 1)]) === s[++n] &&
                              i === s[++n] &&
                              i === s[++n])
                          ) {
                            r = t.strstart + z;
                            do {} while (
                              i === s[++n] &&
                              i === s[++n] &&
                              i === s[++n] &&
                              i === s[++n] &&
                              i === s[++n] &&
                              i === s[++n] &&
                              i === s[++n] &&
                              i === s[++n] &&
                              n < r
                            );
                            (t.match_length = z - (r - n)),
                              t.match_length > t.lookahead && (t.match_length = t.lookahead);
                          }
                          if (
                            (t.match_length >= x
                              ? ((a = h._tr_tally(t, 1, t.match_length - x)),
                                (t.lookahead -= t.match_length),
                                (t.strstart += t.match_length),
                                (t.match_length = 0))
                              : ((a = h._tr_tally(t, 0, t.window[t.strstart])), t.lookahead--, t.strstart++),
                            a && (U(t, !1), 0 === t.strm.avail_out))
                          )
                            return A;
                        }
                        return (
                          (t.insert = 0),
                          e === f
                            ? (U(t, !0), 0 === t.strm.avail_out ? R : C)
                            : t.last_lit && (U(t, !1), 0 === t.strm.avail_out)
                            ? A
                            : Z
                        );
                      })(i, e)
                    : l[i.level].func(i, e);
                if (((o !== R && o !== C) || (i.status = 666), o === A || o === R))
                  return 0 === t.avail_out && (i.last_flush = -1), b;
                if (
                  o === Z &&
                  (1 === e
                    ? h._tr_align(i)
                    : 5 !== e &&
                      (h._tr_stored_block(i, 0, 0, !1),
                      3 === e &&
                        (D(i.head), 0 === i.lookahead && ((i.strstart = 0), (i.block_start = 0), (i.insert = 0)))),
                  I(t),
                  0 === t.avail_out)
                )
                  return (i.last_flush = -1), b;
              }
              return e !== f
                ? b
                : i.wrap <= 0
                ? 1
                : (2 === i.wrap
                    ? (T(i, 255 & t.adler),
                      T(i, (t.adler >> 8) & 255),
                      T(i, (t.adler >> 16) & 255),
                      T(i, (t.adler >> 24) & 255),
                      T(i, 255 & t.total_in),
                      T(i, (t.total_in >> 8) & 255),
                      T(i, (t.total_in >> 16) & 255),
                      T(i, (t.total_in >> 24) & 255))
                    : (F(i, t.adler >>> 16), F(i, 65535 & t.adler)),
                  I(t),
                  0 < i.wrap && (i.wrap = -i.wrap),
                  0 !== i.pending ? b : 1);
            }),
            (a.deflateEnd = function (t) {
              var e;
              return t && t.state
                ? (e = t.state.status) !== S && 69 !== e && 73 !== e && 91 !== e && 103 !== e && e !== E && 666 !== e
                  ? N(t, g)
                  : ((t.state = null), e === E ? N(t, -3) : b)
                : g;
            }),
            (a.deflateSetDictionary = function (t, e) {
              var a,
                i,
                n,
                r,
                s,
                o,
                l,
                h,
                d = e.length;
              if (!t || !t.state) return g;
              if (2 === (r = (a = t.state).wrap) || (1 === r && a.status !== S) || a.lookahead) return g;
              for (
                1 === r && (t.adler = u(t.adler, e, d, 0)),
                  a.wrap = 0,
                  d >= a.w_size &&
                    (0 === r && (D(a.head), (a.strstart = 0), (a.block_start = 0), (a.insert = 0)),
                    (h = new _.Buf8(a.w_size)),
                    _.arraySet(h, e, d - a.w_size, a.w_size, 0),
                    (e = h),
                    (d = a.w_size)),
                  s = t.avail_in,
                  o = t.next_in,
                  l = t.input,
                  t.avail_in = d,
                  t.next_in = 0,
                  t.input = e,
                  H(a);
                a.lookahead >= x;

              ) {
                for (
                  i = a.strstart, n = a.lookahead - (x - 1);
                  (a.ins_h = ((a.ins_h << a.hash_shift) ^ a.window[i + x - 1]) & a.hash_mask),
                    (a.prev[i & a.w_mask] = a.head[a.ins_h]),
                    (a.head[a.ins_h] = i),
                    i++,
                    --n;

                );
                (a.strstart = i), (a.lookahead = x - 1), H(a);
              }
              return (
                (a.strstart += a.lookahead),
                (a.block_start = a.strstart),
                (a.insert = a.lookahead),
                (a.lookahead = 0),
                (a.match_length = a.prev_length = x - 1),
                (a.match_available = 0),
                (t.next_in = o),
                (t.input = l),
                (t.avail_in = s),
                (a.wrap = r),
                b
              );
            }),
            (a.deflateInfo = 'pako deflate (from Nodeca project)');
        },
        { '../utils/common': 3, './adler32': 5, './crc32': 7, './messages': 13, './trees': 14 },
      ],
      '9': [
        function (t, e, a) {
          'use strict';
          e.exports = function () {
            (this.text = 0),
              (this.time = 0),
              (this.xflags = 0),
              (this.os = 0),
              (this.extra = null),
              (this.extra_len = 0),
              (this.name = ''),
              (this.comment = ''),
              (this.hcrc = 0),
              (this.done = !1);
          };
        },
        {},
      ],
      '10': [
        function (t, e, a) {
          'use strict';
          e.exports = function (t, e) {
            var a, i, n, r, s, o, l, h, d, f, _, u, c, b, g, m, w, p, v, k, y, x, z, B, S;
            (a = t.state),
              (i = t.next_in),
              (B = t.input),
              (n = i + (t.avail_in - 5)),
              (r = t.next_out),
              (S = t.output),
              (s = r - (e - t.avail_out)),
              (o = r + (t.avail_out - 257)),
              (l = a.dmax),
              (h = a.wsize),
              (d = a.whave),
              (f = a.wnext),
              (_ = a.window),
              (u = a.hold),
              (c = a.bits),
              (b = a.lencode),
              (g = a.distcode),
              (m = (1 << a.lenbits) - 1),
              (w = (1 << a.distbits) - 1);
            t: do {
              c < 15 && ((u += B[i++] << c), (c += 8), (u += B[i++] << c), (c += 8)), (p = b[u & m]);
              e: for (;;) {
                if (((u >>>= v = p >>> 24), (c -= v), 0 === (v = (p >>> 16) & 255))) S[r++] = 65535 & p;
                else {
                  if (!(16 & v)) {
                    if (0 == (64 & v)) {
                      p = b[(65535 & p) + (u & ((1 << v) - 1))];
                      continue e;
                    }
                    if (32 & v) {
                      a.mode = 12;
                      break t;
                    }
                    (t.msg = 'invalid literal/length code'), (a.mode = 30);
                    break t;
                  }
                  (k = 65535 & p),
                    (v &= 15) &&
                      (c < v && ((u += B[i++] << c), (c += 8)), (k += u & ((1 << v) - 1)), (u >>>= v), (c -= v)),
                    c < 15 && ((u += B[i++] << c), (c += 8), (u += B[i++] << c), (c += 8)),
                    (p = g[u & w]);
                  a: for (;;) {
                    if (((u >>>= v = p >>> 24), (c -= v), !(16 & (v = (p >>> 16) & 255)))) {
                      if (0 == (64 & v)) {
                        p = g[(65535 & p) + (u & ((1 << v) - 1))];
                        continue a;
                      }
                      (t.msg = 'invalid distance code'), (a.mode = 30);
                      break t;
                    }
                    if (
                      ((y = 65535 & p),
                      c < (v &= 15) && ((u += B[i++] << c), (c += 8) < v && ((u += B[i++] << c), (c += 8))),
                      l < (y += u & ((1 << v) - 1)))
                    ) {
                      (t.msg = 'invalid distance too far back'), (a.mode = 30);
                      break t;
                    }
                    if (((u >>>= v), (c -= v), (v = r - s) < y)) {
                      if (d < (v = y - v) && a.sane) {
                        (t.msg = 'invalid distance too far back'), (a.mode = 30);
                        break t;
                      }
                      if (((z = _), (x = 0) === f)) {
                        if (((x += h - v), v < k)) {
                          for (k -= v; (S[r++] = _[x++]), --v; );
                          (x = r - y), (z = S);
                        }
                      } else if (f < v) {
                        if (((x += h + f - v), (v -= f) < k)) {
                          for (k -= v; (S[r++] = _[x++]), --v; );
                          if (((x = 0), f < k)) {
                            for (k -= v = f; (S[r++] = _[x++]), --v; );
                            (x = r - y), (z = S);
                          }
                        }
                      } else if (((x += f - v), v < k)) {
                        for (k -= v; (S[r++] = _[x++]), --v; );
                        (x = r - y), (z = S);
                      }
                      for (; 2 < k; ) (S[r++] = z[x++]), (S[r++] = z[x++]), (S[r++] = z[x++]), (k -= 3);
                      k && ((S[r++] = z[x++]), 1 < k && (S[r++] = z[x++]));
                    } else {
                      for (x = r - y; (S[r++] = S[x++]), (S[r++] = S[x++]), (S[r++] = S[x++]), 2 < (k -= 3); );
                      k && ((S[r++] = S[x++]), 1 < k && (S[r++] = S[x++]));
                    }
                    break;
                  }
                }
                break;
              }
            } while (i < n && r < o);
            (i -= k = c >> 3),
              (u &= (1 << (c -= k << 3)) - 1),
              (t.next_in = i),
              (t.next_out = r),
              (t.avail_in = i < n ? n - i + 5 : 5 - (i - n)),
              (t.avail_out = r < o ? o - r + 257 : 257 - (r - o)),
              (a.hold = u),
              (a.bits = c);
          };
        },
        {},
      ],
      '11': [
        function (t, e, a) {
          'use strict';
          var Z = t('../utils/common'),
            R = t('./adler32'),
            C = t('./crc32'),
            N = t('./inffast'),
            O = t('./inftrees'),
            D = 1,
            I = 2,
            U = 0,
            T = -2,
            F = 1,
            i = 852,
            n = 592;
          function L(t) {
            return ((t >>> 24) & 255) + ((t >>> 8) & 65280) + ((65280 & t) << 8) + ((255 & t) << 24);
          }
          function r() {
            (this.mode = 0),
              (this.last = !1),
              (this.wrap = 0),
              (this.havedict = !1),
              (this.flags = 0),
              (this.dmax = 0),
              (this.check = 0),
              (this.total = 0),
              (this.head = null),
              (this.wbits = 0),
              (this.wsize = 0),
              (this.whave = 0),
              (this.wnext = 0),
              (this.window = null),
              (this.hold = 0),
              (this.bits = 0),
              (this.length = 0),
              (this.offset = 0),
              (this.extra = 0),
              (this.lencode = null),
              (this.distcode = null),
              (this.lenbits = 0),
              (this.distbits = 0),
              (this.ncode = 0),
              (this.nlen = 0),
              (this.ndist = 0),
              (this.have = 0),
              (this.next = null),
              (this.lens = new Z.Buf16(320)),
              (this.work = new Z.Buf16(288)),
              (this.lendyn = null),
              (this.distdyn = null),
              (this.sane = 0),
              (this.back = 0),
              (this.was = 0);
          }
          function s(t) {
            var e;
            return t && t.state
              ? ((e = t.state),
                (t.total_in = t.total_out = e.total = 0),
                (t.msg = ''),
                e.wrap && (t.adler = 1 & e.wrap),
                (e.mode = F),
                (e.last = 0),
                (e.havedict = 0),
                (e.dmax = 32768),
                (e.head = null),
                (e.hold = 0),
                (e.bits = 0),
                (e.lencode = e.lendyn = new Z.Buf32(i)),
                (e.distcode = e.distdyn = new Z.Buf32(n)),
                (e.sane = 1),
                (e.back = -1),
                U)
              : T;
          }
          function o(t) {
            var e;
            return t && t.state ? (((e = t.state).wsize = 0), (e.whave = 0), (e.wnext = 0), s(t)) : T;
          }
          function l(t, e) {
            var a, i;
            return t && t.state
              ? ((i = t.state),
                e < 0 ? ((a = 0), (e = -e)) : ((a = 1 + (e >> 4)), e < 48 && (e &= 15)),
                e && (e < 8 || 15 < e)
                  ? T
                  : (null !== i.window && i.wbits !== e && (i.window = null), (i.wrap = a), (i.wbits = e), o(t)))
              : T;
          }
          function h(t, e) {
            var a, i;
            return t ? ((i = new r()), ((t.state = i).window = null), (a = l(t, e)) !== U && (t.state = null), a) : T;
          }
          var d,
            f,
            _ = !0;
          function H(t) {
            if (_) {
              var e;
              for (d = new Z.Buf32(512), f = new Z.Buf32(32), e = 0; e < 144; ) t.lens[e++] = 8;
              for (; e < 256; ) t.lens[e++] = 9;
              for (; e < 280; ) t.lens[e++] = 7;
              for (; e < 288; ) t.lens[e++] = 8;
              for (O(D, t.lens, 0, 288, d, 0, t.work, { bits: 9 }), e = 0; e < 32; ) t.lens[e++] = 5;
              O(I, t.lens, 0, 32, f, 0, t.work, { bits: 5 }), (_ = !1);
            }
            (t.lencode = d), (t.lenbits = 9), (t.distcode = f), (t.distbits = 5);
          }
          function j(t, e, a, i) {
            var n,
              r = t.state;
            return (
              null === r.window &&
                ((r.wsize = 1 << r.wbits), (r.wnext = 0), (r.whave = 0), (r.window = new Z.Buf8(r.wsize))),
              i >= r.wsize
                ? (Z.arraySet(r.window, e, a - r.wsize, r.wsize, 0), (r.wnext = 0), (r.whave = r.wsize))
                : (i < (n = r.wsize - r.wnext) && (n = i),
                  Z.arraySet(r.window, e, a - i, n, r.wnext),
                  (i -= n)
                    ? (Z.arraySet(r.window, e, a - i, i, 0), (r.wnext = i), (r.whave = r.wsize))
                    : ((r.wnext += n), r.wnext === r.wsize && (r.wnext = 0), r.whave < r.wsize && (r.whave += n))),
              0
            );
          }
          (a.inflateReset = o),
            (a.inflateReset2 = l),
            (a.inflateResetKeep = s),
            (a.inflateInit = function (t) {
              return h(t, 15);
            }),
            (a.inflateInit2 = h),
            (a.inflate = function (t, e) {
              var a,
                i,
                n,
                r,
                s,
                o,
                l,
                h,
                d,
                f,
                _,
                u,
                c,
                b,
                g,
                m,
                w,
                p,
                v,
                k,
                y,
                x,
                z,
                B,
                S = 0,
                E = new Z.Buf8(4),
                A = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
              if (!t || !t.state || !t.output || (!t.input && 0 !== t.avail_in)) return T;
              12 === (a = t.state).mode && (a.mode = 13),
                (s = t.next_out),
                (n = t.output),
                (l = t.avail_out),
                (r = t.next_in),
                (i = t.input),
                (o = t.avail_in),
                (h = a.hold),
                (d = a.bits),
                (f = o),
                (_ = l),
                (x = U);
              t: for (;;)
                switch (a.mode) {
                  case F:
                    if (0 === a.wrap) {
                      a.mode = 13;
                      break;
                    }
                    for (; d < 16; ) {
                      if (0 === o) break t;
                      o--, (h += i[r++] << d), (d += 8);
                    }
                    if (2 & a.wrap && 35615 === h) {
                      (E[(a.check = 0)] = 255 & h),
                        (E[1] = (h >>> 8) & 255),
                        (a.check = C(a.check, E, 2, 0)),
                        (d = h = 0),
                        (a.mode = 2);
                      break;
                    }
                    if (
                      ((a.flags = 0), a.head && (a.head.done = !1), !(1 & a.wrap) || (((255 & h) << 8) + (h >> 8)) % 31)
                    ) {
                      (t.msg = 'incorrect header check'), (a.mode = 30);
                      break;
                    }
                    if (8 != (15 & h)) {
                      (t.msg = 'unknown compression method'), (a.mode = 30);
                      break;
                    }
                    if (((d -= 4), (y = 8 + (15 & (h >>>= 4))), 0 === a.wbits)) a.wbits = y;
                    else if (y > a.wbits) {
                      (t.msg = 'invalid window size'), (a.mode = 30);
                      break;
                    }
                    (a.dmax = 1 << y), (t.adler = a.check = 1), (a.mode = 512 & h ? 10 : 12), (d = h = 0);
                    break;
                  case 2:
                    for (; d < 16; ) {
                      if (0 === o) break t;
                      o--, (h += i[r++] << d), (d += 8);
                    }
                    if (((a.flags = h), 8 != (255 & a.flags))) {
                      (t.msg = 'unknown compression method'), (a.mode = 30);
                      break;
                    }
                    if (57344 & a.flags) {
                      (t.msg = 'unknown header flags set'), (a.mode = 30);
                      break;
                    }
                    a.head && (a.head.text = (h >> 8) & 1),
                      512 & a.flags && ((E[0] = 255 & h), (E[1] = (h >>> 8) & 255), (a.check = C(a.check, E, 2, 0))),
                      (d = h = 0),
                      (a.mode = 3);
                  case 3:
                    for (; d < 32; ) {
                      if (0 === o) break t;
                      o--, (h += i[r++] << d), (d += 8);
                    }
                    a.head && (a.head.time = h),
                      512 & a.flags &&
                        ((E[0] = 255 & h),
                        (E[1] = (h >>> 8) & 255),
                        (E[2] = (h >>> 16) & 255),
                        (E[3] = (h >>> 24) & 255),
                        (a.check = C(a.check, E, 4, 0))),
                      (d = h = 0),
                      (a.mode = 4);
                  case 4:
                    for (; d < 16; ) {
                      if (0 === o) break t;
                      o--, (h += i[r++] << d), (d += 8);
                    }
                    a.head && ((a.head.xflags = 255 & h), (a.head.os = h >> 8)),
                      512 & a.flags && ((E[0] = 255 & h), (E[1] = (h >>> 8) & 255), (a.check = C(a.check, E, 2, 0))),
                      (d = h = 0),
                      (a.mode = 5);
                  case 5:
                    if (1024 & a.flags) {
                      for (; d < 16; ) {
                        if (0 === o) break t;
                        o--, (h += i[r++] << d), (d += 8);
                      }
                      (a.length = h),
                        a.head && (a.head.extra_len = h),
                        512 & a.flags && ((E[0] = 255 & h), (E[1] = (h >>> 8) & 255), (a.check = C(a.check, E, 2, 0))),
                        (d = h = 0);
                    } else a.head && (a.head.extra = null);
                    a.mode = 6;
                  case 6:
                    if (
                      1024 & a.flags &&
                      (o < (u = a.length) && (u = o),
                      u &&
                        (a.head &&
                          ((y = a.head.extra_len - a.length),
                          a.head.extra || (a.head.extra = new Array(a.head.extra_len)),
                          Z.arraySet(a.head.extra, i, r, u, y)),
                        512 & a.flags && (a.check = C(a.check, i, u, r)),
                        (o -= u),
                        (r += u),
                        (a.length -= u)),
                      a.length)
                    )
                      break t;
                    (a.length = 0), (a.mode = 7);
                  case 7:
                    if (2048 & a.flags) {
                      if (0 === o) break t;
                      for (
                        u = 0;
                        (y = i[r + u++]),
                          a.head && y && a.length < 65536 && (a.head.name += String.fromCharCode(y)),
                          y && u < o;

                      );
                      if ((512 & a.flags && (a.check = C(a.check, i, u, r)), (o -= u), (r += u), y)) break t;
                    } else a.head && (a.head.name = null);
                    (a.length = 0), (a.mode = 8);
                  case 8:
                    if (4096 & a.flags) {
                      if (0 === o) break t;
                      for (
                        u = 0;
                        (y = i[r + u++]),
                          a.head && y && a.length < 65536 && (a.head.comment += String.fromCharCode(y)),
                          y && u < o;

                      );
                      if ((512 & a.flags && (a.check = C(a.check, i, u, r)), (o -= u), (r += u), y)) break t;
                    } else a.head && (a.head.comment = null);
                    a.mode = 9;
                  case 9:
                    if (512 & a.flags) {
                      for (; d < 16; ) {
                        if (0 === o) break t;
                        o--, (h += i[r++] << d), (d += 8);
                      }
                      if (h !== (65535 & a.check)) {
                        (t.msg = 'header crc mismatch'), (a.mode = 30);
                        break;
                      }
                      d = h = 0;
                    }
                    a.head && ((a.head.hcrc = (a.flags >> 9) & 1), (a.head.done = !0)),
                      (t.adler = a.check = 0),
                      (a.mode = 12);
                    break;
                  case 10:
                    for (; d < 32; ) {
                      if (0 === o) break t;
                      o--, (h += i[r++] << d), (d += 8);
                    }
                    (t.adler = a.check = L(h)), (d = h = 0), (a.mode = 11);
                  case 11:
                    if (0 === a.havedict)
                      return (
                        (t.next_out = s),
                        (t.avail_out = l),
                        (t.next_in = r),
                        (t.avail_in = o),
                        (a.hold = h),
                        (a.bits = d),
                        2
                      );
                    (t.adler = a.check = 1), (a.mode = 12);
                  case 12:
                    if (5 === e || 6 === e) break t;
                  case 13:
                    if (a.last) {
                      (h >>>= 7 & d), (d -= 7 & d), (a.mode = 27);
                      break;
                    }
                    for (; d < 3; ) {
                      if (0 === o) break t;
                      o--, (h += i[r++] << d), (d += 8);
                    }
                    switch (((a.last = 1 & h), (d -= 1), 3 & (h >>>= 1))) {
                      case 0:
                        a.mode = 14;
                        break;
                      case 1:
                        if ((H(a), (a.mode = 20), 6 !== e)) break;
                        (h >>>= 2), (d -= 2);
                        break t;
                      case 2:
                        a.mode = 17;
                        break;
                      case 3:
                        (t.msg = 'invalid block type'), (a.mode = 30);
                    }
                    (h >>>= 2), (d -= 2);
                    break;
                  case 14:
                    for (h >>>= 7 & d, d -= 7 & d; d < 32; ) {
                      if (0 === o) break t;
                      o--, (h += i[r++] << d), (d += 8);
                    }
                    if ((65535 & h) != ((h >>> 16) ^ 65535)) {
                      (t.msg = 'invalid stored block lengths'), (a.mode = 30);
                      break;
                    }
                    if (((a.length = 65535 & h), (d = h = 0), (a.mode = 15), 6 === e)) break t;
                  case 15:
                    a.mode = 16;
                  case 16:
                    if ((u = a.length)) {
                      if ((o < u && (u = o), l < u && (u = l), 0 === u)) break t;
                      Z.arraySet(n, i, r, u, s), (o -= u), (r += u), (l -= u), (s += u), (a.length -= u);
                      break;
                    }
                    a.mode = 12;
                    break;
                  case 17:
                    for (; d < 14; ) {
                      if (0 === o) break t;
                      o--, (h += i[r++] << d), (d += 8);
                    }
                    if (
                      ((a.nlen = 257 + (31 & h)),
                      (h >>>= 5),
                      (d -= 5),
                      (a.ndist = 1 + (31 & h)),
                      (h >>>= 5),
                      (d -= 5),
                      (a.ncode = 4 + (15 & h)),
                      (h >>>= 4),
                      (d -= 4),
                      286 < a.nlen || 30 < a.ndist)
                    ) {
                      (t.msg = 'too many length or distance symbols'), (a.mode = 30);
                      break;
                    }
                    (a.have = 0), (a.mode = 18);
                  case 18:
                    for (; a.have < a.ncode; ) {
                      for (; d < 3; ) {
                        if (0 === o) break t;
                        o--, (h += i[r++] << d), (d += 8);
                      }
                      (a.lens[A[a.have++]] = 7 & h), (h >>>= 3), (d -= 3);
                    }
                    for (; a.have < 19; ) a.lens[A[a.have++]] = 0;
                    if (
                      ((a.lencode = a.lendyn),
                      (a.lenbits = 7),
                      (z = { bits: a.lenbits }),
                      (x = O(0, a.lens, 0, 19, a.lencode, 0, a.work, z)),
                      (a.lenbits = z.bits),
                      x)
                    ) {
                      (t.msg = 'invalid code lengths set'), (a.mode = 30);
                      break;
                    }
                    (a.have = 0), (a.mode = 19);
                  case 19:
                    for (; a.have < a.nlen + a.ndist; ) {
                      for (
                        ;
                        (m = ((S = a.lencode[h & ((1 << a.lenbits) - 1)]) >>> 16) & 255),
                          (w = 65535 & S),
                          !((g = S >>> 24) <= d);

                      ) {
                        if (0 === o) break t;
                        o--, (h += i[r++] << d), (d += 8);
                      }
                      if (w < 16) (h >>>= g), (d -= g), (a.lens[a.have++] = w);
                      else {
                        if (16 === w) {
                          for (B = g + 2; d < B; ) {
                            if (0 === o) break t;
                            o--, (h += i[r++] << d), (d += 8);
                          }
                          if (((h >>>= g), (d -= g), 0 === a.have)) {
                            (t.msg = 'invalid bit length repeat'), (a.mode = 30);
                            break;
                          }
                          (y = a.lens[a.have - 1]), (u = 3 + (3 & h)), (h >>>= 2), (d -= 2);
                        } else if (17 === w) {
                          for (B = g + 3; d < B; ) {
                            if (0 === o) break t;
                            o--, (h += i[r++] << d), (d += 8);
                          }
                          (d -= g), (y = 0), (u = 3 + (7 & (h >>>= g))), (h >>>= 3), (d -= 3);
                        } else {
                          for (B = g + 7; d < B; ) {
                            if (0 === o) break t;
                            o--, (h += i[r++] << d), (d += 8);
                          }
                          (d -= g), (y = 0), (u = 11 + (127 & (h >>>= g))), (h >>>= 7), (d -= 7);
                        }
                        if (a.have + u > a.nlen + a.ndist) {
                          (t.msg = 'invalid bit length repeat'), (a.mode = 30);
                          break;
                        }
                        for (; u--; ) a.lens[a.have++] = y;
                      }
                    }
                    if (30 === a.mode) break;
                    if (0 === a.lens[256]) {
                      (t.msg = 'invalid code -- missing end-of-block'), (a.mode = 30);
                      break;
                    }
                    if (
                      ((a.lenbits = 9),
                      (z = { bits: a.lenbits }),
                      (x = O(D, a.lens, 0, a.nlen, a.lencode, 0, a.work, z)),
                      (a.lenbits = z.bits),
                      x)
                    ) {
                      (t.msg = 'invalid literal/lengths set'), (a.mode = 30);
                      break;
                    }
                    if (
                      ((a.distbits = 6),
                      (a.distcode = a.distdyn),
                      (z = { bits: a.distbits }),
                      (x = O(I, a.lens, a.nlen, a.ndist, a.distcode, 0, a.work, z)),
                      (a.distbits = z.bits),
                      x)
                    ) {
                      (t.msg = 'invalid distances set'), (a.mode = 30);
                      break;
                    }
                    if (((a.mode = 20), 6 === e)) break t;
                  case 20:
                    a.mode = 21;
                  case 21:
                    if (6 <= o && 258 <= l) {
                      (t.next_out = s),
                        (t.avail_out = l),
                        (t.next_in = r),
                        (t.avail_in = o),
                        (a.hold = h),
                        (a.bits = d),
                        N(t, _),
                        (s = t.next_out),
                        (n = t.output),
                        (l = t.avail_out),
                        (r = t.next_in),
                        (i = t.input),
                        (o = t.avail_in),
                        (h = a.hold),
                        (d = a.bits),
                        12 === a.mode && (a.back = -1);
                      break;
                    }
                    for (
                      a.back = 0;
                      (m = ((S = a.lencode[h & ((1 << a.lenbits) - 1)]) >>> 16) & 255),
                        (w = 65535 & S),
                        !((g = S >>> 24) <= d);

                    ) {
                      if (0 === o) break t;
                      o--, (h += i[r++] << d), (d += 8);
                    }
                    if (m && 0 == (240 & m)) {
                      for (
                        p = g, v = m, k = w;
                        (m = ((S = a.lencode[k + ((h & ((1 << (p + v)) - 1)) >> p)]) >>> 16) & 255),
                          (w = 65535 & S),
                          !(p + (g = S >>> 24) <= d);

                      ) {
                        if (0 === o) break t;
                        o--, (h += i[r++] << d), (d += 8);
                      }
                      (h >>>= p), (d -= p), (a.back += p);
                    }
                    if (((h >>>= g), (d -= g), (a.back += g), (a.length = w), 0 === m)) {
                      a.mode = 26;
                      break;
                    }
                    if (32 & m) {
                      (a.back = -1), (a.mode = 12);
                      break;
                    }
                    if (64 & m) {
                      (t.msg = 'invalid literal/length code'), (a.mode = 30);
                      break;
                    }
                    (a.extra = 15 & m), (a.mode = 22);
                  case 22:
                    if (a.extra) {
                      for (B = a.extra; d < B; ) {
                        if (0 === o) break t;
                        o--, (h += i[r++] << d), (d += 8);
                      }
                      (a.length += h & ((1 << a.extra) - 1)), (h >>>= a.extra), (d -= a.extra), (a.back += a.extra);
                    }
                    (a.was = a.length), (a.mode = 23);
                  case 23:
                    for (
                      ;
                      (m = ((S = a.distcode[h & ((1 << a.distbits) - 1)]) >>> 16) & 255),
                        (w = 65535 & S),
                        !((g = S >>> 24) <= d);

                    ) {
                      if (0 === o) break t;
                      o--, (h += i[r++] << d), (d += 8);
                    }
                    if (0 == (240 & m)) {
                      for (
                        p = g, v = m, k = w;
                        (m = ((S = a.distcode[k + ((h & ((1 << (p + v)) - 1)) >> p)]) >>> 16) & 255),
                          (w = 65535 & S),
                          !(p + (g = S >>> 24) <= d);

                      ) {
                        if (0 === o) break t;
                        o--, (h += i[r++] << d), (d += 8);
                      }
                      (h >>>= p), (d -= p), (a.back += p);
                    }
                    if (((h >>>= g), (d -= g), (a.back += g), 64 & m)) {
                      (t.msg = 'invalid distance code'), (a.mode = 30);
                      break;
                    }
                    (a.offset = w), (a.extra = 15 & m), (a.mode = 24);
                  case 24:
                    if (a.extra) {
                      for (B = a.extra; d < B; ) {
                        if (0 === o) break t;
                        o--, (h += i[r++] << d), (d += 8);
                      }
                      (a.offset += h & ((1 << a.extra) - 1)), (h >>>= a.extra), (d -= a.extra), (a.back += a.extra);
                    }
                    if (a.offset > a.dmax) {
                      (t.msg = 'invalid distance too far back'), (a.mode = 30);
                      break;
                    }
                    a.mode = 25;
                  case 25:
                    if (0 === l) break t;
                    if (((u = _ - l), a.offset > u)) {
                      if ((u = a.offset - u) > a.whave && a.sane) {
                        (t.msg = 'invalid distance too far back'), (a.mode = 30);
                        break;
                      }
                      u > a.wnext ? ((u -= a.wnext), (c = a.wsize - u)) : (c = a.wnext - u),
                        u > a.length && (u = a.length),
                        (b = a.window);
                    } else (b = n), (c = s - a.offset), (u = a.length);
                    for (l < u && (u = l), l -= u, a.length -= u; (n[s++] = b[c++]), --u; );
                    0 === a.length && (a.mode = 21);
                    break;
                  case 26:
                    if (0 === l) break t;
                    (n[s++] = a.length), l--, (a.mode = 21);
                    break;
                  case 27:
                    if (a.wrap) {
                      for (; d < 32; ) {
                        if (0 === o) break t;
                        o--, (h |= i[r++] << d), (d += 8);
                      }
                      if (
                        ((_ -= l),
                        (t.total_out += _),
                        (a.total += _),
                        _ && (t.adler = a.check = a.flags ? C(a.check, n, _, s - _) : R(a.check, n, _, s - _)),
                        (_ = l),
                        (a.flags ? h : L(h)) !== a.check)
                      ) {
                        (t.msg = 'incorrect data check'), (a.mode = 30);
                        break;
                      }
                      d = h = 0;
                    }
                    a.mode = 28;
                  case 28:
                    if (a.wrap && a.flags) {
                      for (; d < 32; ) {
                        if (0 === o) break t;
                        o--, (h += i[r++] << d), (d += 8);
                      }
                      if (h !== (4294967295 & a.total)) {
                        (t.msg = 'incorrect length check'), (a.mode = 30);
                        break;
                      }
                      d = h = 0;
                    }
                    a.mode = 29;
                  case 29:
                    x = 1;
                    break t;
                  case 30:
                    x = -3;
                    break t;
                  case 31:
                    return -4;
                  case 32:
                  default:
                    return T;
                }
              return (
                (t.next_out = s),
                (t.avail_out = l),
                (t.next_in = r),
                (t.avail_in = o),
                (a.hold = h),
                (a.bits = d),
                (a.wsize || (_ !== t.avail_out && a.mode < 30 && (a.mode < 27 || 4 !== e))) &&
                j(t, t.output, t.next_out, _ - t.avail_out)
                  ? ((a.mode = 31), -4)
                  : ((f -= t.avail_in),
                    (_ -= t.avail_out),
                    (t.total_in += f),
                    (t.total_out += _),
                    (a.total += _),
                    a.wrap &&
                      _ &&
                      (t.adler = a.check =
                        a.flags ? C(a.check, n, _, t.next_out - _) : R(a.check, n, _, t.next_out - _)),
                    (t.data_type =
                      a.bits +
                      (a.last ? 64 : 0) +
                      (12 === a.mode ? 128 : 0) +
                      (20 === a.mode || 15 === a.mode ? 256 : 0)),
                    ((0 === f && 0 === _) || 4 === e) && x === U && (x = -5),
                    x)
              );
            }),
            (a.inflateEnd = function (t) {
              if (!t || !t.state) return T;
              var e = t.state;
              return e.window && (e.window = null), (t.state = null), U;
            }),
            (a.inflateGetHeader = function (t, e) {
              var a;
              return t && t.state ? (0 == (2 & (a = t.state).wrap) ? T : (((a.head = e).done = !1), U)) : T;
            }),
            (a.inflateSetDictionary = function (t, e) {
              var a,
                i = e.length;
              return t && t.state
                ? 0 !== (a = t.state).wrap && 11 !== a.mode
                  ? T
                  : 11 === a.mode && R(1, e, i, 0) !== a.check
                  ? -3
                  : j(t, e, i, i)
                  ? ((a.mode = 31), -4)
                  : ((a.havedict = 1), U)
                : T;
            }),
            (a.inflateInfo = 'pako inflate (from Nodeca project)');
        },
        { '../utils/common': 3, './adler32': 5, './crc32': 7, './inffast': 10, './inftrees': 12 },
      ],
      '12': [
        function (t, e, a) {
          'use strict';
          var D = t('../utils/common'),
            I = [
              3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195,
              227, 258, 0, 0,
            ],
            U = [
              16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21,
              21, 21, 16, 72, 78,
            ],
            T = [
              1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073,
              4097, 6145, 8193, 12289, 16385, 24577, 0, 0,
            ],
            F = [
              16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
              28, 28, 29, 29, 64, 64,
            ];
          e.exports = function (t, e, a, i, n, r, s, o) {
            var l,
              h,
              d,
              f,
              _,
              u,
              c,
              b,
              g,
              m = o.bits,
              w = 0,
              p = 0,
              v = 0,
              k = 0,
              y = 0,
              x = 0,
              z = 0,
              B = 0,
              S = 0,
              E = 0,
              A = null,
              Z = 0,
              R = new D.Buf16(16),
              C = new D.Buf16(16),
              N = null,
              O = 0;
            for (w = 0; w <= 15; w++) R[w] = 0;
            for (p = 0; p < i; p++) R[e[a + p]]++;
            for (y = m, k = 15; 1 <= k && 0 === R[k]; k--);
            if ((k < y && (y = k), 0 === k)) return (n[r++] = 20971520), (n[r++] = 20971520), (o.bits = 1), 0;
            for (v = 1; v < k && 0 === R[v]; v++);
            for (y < v && (y = v), w = B = 1; w <= 15; w++) if (((B <<= 1), (B -= R[w]) < 0)) return -1;
            if (0 < B && (0 === t || 1 !== k)) return -1;
            for (C[1] = 0, w = 1; w < 15; w++) C[w + 1] = C[w] + R[w];
            for (p = 0; p < i; p++) 0 !== e[a + p] && (s[C[e[a + p]]++] = p);
            if (
              (0 === t
                ? ((A = N = s), (u = 19))
                : 1 === t
                ? ((A = I), (Z -= 257), (N = U), (O -= 257), (u = 256))
                : ((A = T), (N = F), (u = -1)),
              (w = v),
              (_ = r),
              (z = p = E = 0),
              (d = -1),
              (f = (S = 1 << (x = y)) - 1),
              (1 === t && 852 < S) || (2 === t && 592 < S))
            )
              return 1;
            for (;;) {
              for (
                c = w - z,
                  s[p] < u
                    ? ((b = 0), (g = s[p]))
                    : s[p] > u
                    ? ((b = N[O + s[p]]), (g = A[Z + s[p]]))
                    : ((b = 96), (g = 0)),
                  l = 1 << (w - z),
                  v = h = 1 << x;
                (n[_ + (E >> z) + (h -= l)] = (c << 24) | (b << 16) | g | 0), 0 !== h;

              );
              for (l = 1 << (w - 1); E & l; ) l >>= 1;
              if ((0 !== l ? ((E &= l - 1), (E += l)) : (E = 0), p++, 0 == --R[w])) {
                if (w === k) break;
                w = e[a + s[p]];
              }
              if (y < w && (E & f) !== d) {
                for (0 === z && (z = y), _ += v, B = 1 << (x = w - z); x + z < k && !((B -= R[x + z]) <= 0); )
                  x++, (B <<= 1);
                if (((S += 1 << x), (1 === t && 852 < S) || (2 === t && 592 < S))) return 1;
                n[(d = E & f)] = (y << 24) | (x << 16) | (_ - r) | 0;
              }
            }
            return 0 !== E && (n[_ + E] = ((w - z) << 24) | (64 << 16) | 0), (o.bits = y), 0;
          };
        },
        { '../utils/common': 3 },
      ],
      '13': [
        function (t, e, a) {
          'use strict';
          e.exports = {
            '2': 'need dictionary',
            '1': 'stream end',
            '0': '',
            '-1': 'file error',
            '-2': 'stream error',
            '-3': 'data error',
            '-4': 'insufficient memory',
            '-5': 'buffer error',
            '-6': 'incompatible version',
          };
        },
        {},
      ],
      '14': [
        function (t, e, a) {
          'use strict';
          var l = t('../utils/common'),
            o = 0,
            h = 1;
          function i(t) {
            for (var e = t.length; 0 <= --e; ) t[e] = 0;
          }
          var d = 0,
            s = 29,
            f = 256,
            _ = f + 1 + s,
            u = 30,
            c = 19,
            g = 2 * _ + 1,
            m = 15,
            n = 16,
            b = 7,
            w = 256,
            p = 16,
            v = 17,
            k = 18,
            y = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0],
            x = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13],
            z = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7],
            B = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15],
            S = new Array(2 * (_ + 2));
          i(S);
          var E = new Array(2 * u);
          i(E);
          var A = new Array(512);
          i(A);
          var Z = new Array(256);
          i(Z);
          var R = new Array(s);
          i(R);
          var C,
            N,
            O,
            D = new Array(u);
          function I(t, e, a, i, n) {
            (this.static_tree = t),
              (this.extra_bits = e),
              (this.extra_base = a),
              (this.elems = i),
              (this.max_length = n),
              (this.has_stree = t && t.length);
          }
          function r(t, e) {
            (this.dyn_tree = t), (this.max_code = 0), (this.stat_desc = e);
          }
          function U(t) {
            return t < 256 ? A[t] : A[256 + (t >>> 7)];
          }
          function T(t, e) {
            (t.pending_buf[t.pending++] = 255 & e), (t.pending_buf[t.pending++] = (e >>> 8) & 255);
          }
          function F(t, e, a) {
            t.bi_valid > n - a
              ? ((t.bi_buf |= (e << t.bi_valid) & 65535),
                T(t, t.bi_buf),
                (t.bi_buf = e >> (n - t.bi_valid)),
                (t.bi_valid += a - n))
              : ((t.bi_buf |= (e << t.bi_valid) & 65535), (t.bi_valid += a));
          }
          function L(t, e, a) {
            F(t, a[2 * e], a[2 * e + 1]);
          }
          function H(t, e) {
            for (var a = 0; (a |= 1 & t), (t >>>= 1), (a <<= 1), 0 < --e; );
            return a >>> 1;
          }
          function j(t, e, a) {
            var i,
              n,
              r = new Array(m + 1),
              s = 0;
            for (i = 1; i <= m; i++) r[i] = s = (s + a[i - 1]) << 1;
            for (n = 0; n <= e; n++) {
              var o = t[2 * n + 1];
              0 !== o && (t[2 * n] = H(r[o]++, o));
            }
          }
          function K(t) {
            var e;
            for (e = 0; e < _; e++) t.dyn_ltree[2 * e] = 0;
            for (e = 0; e < u; e++) t.dyn_dtree[2 * e] = 0;
            for (e = 0; e < c; e++) t.bl_tree[2 * e] = 0;
            (t.dyn_ltree[2 * w] = 1), (t.opt_len = t.static_len = 0), (t.last_lit = t.matches = 0);
          }
          function M(t) {
            8 < t.bi_valid ? T(t, t.bi_buf) : 0 < t.bi_valid && (t.pending_buf[t.pending++] = t.bi_buf),
              (t.bi_buf = 0),
              (t.bi_valid = 0);
          }
          function P(t, e, a, i) {
            var n = 2 * e,
              r = 2 * a;
            return t[n] < t[r] || (t[n] === t[r] && i[e] <= i[a]);
          }
          function Y(t, e, a) {
            for (
              var i = t.heap[a], n = a << 1;
              n <= t.heap_len &&
              (n < t.heap_len && P(e, t.heap[n + 1], t.heap[n], t.depth) && n++, !P(e, i, t.heap[n], t.depth));

            )
              (t.heap[a] = t.heap[n]), (a = n), (n <<= 1);
            t.heap[a] = i;
          }
          function q(t, e, a) {
            var i,
              n,
              r,
              s,
              o = 0;
            if (0 !== t.last_lit)
              for (
                ;
                (i = (t.pending_buf[t.d_buf + 2 * o] << 8) | t.pending_buf[t.d_buf + 2 * o + 1]),
                  (n = t.pending_buf[t.l_buf + o]),
                  o++,
                  0 === i
                    ? L(t, n, e)
                    : (L(t, (r = Z[n]) + f + 1, e),
                      0 !== (s = y[r]) && F(t, (n -= R[r]), s),
                      L(t, (r = U(--i)), a),
                      0 !== (s = x[r]) && F(t, (i -= D[r]), s)),
                  o < t.last_lit;

              );
            L(t, w, e);
          }
          function G(t, e) {
            var a,
              i,
              n,
              r = e.dyn_tree,
              s = e.stat_desc.static_tree,
              o = e.stat_desc.has_stree,
              l = e.stat_desc.elems,
              h = -1;
            for (t.heap_len = 0, t.heap_max = g, a = 0; a < l; a++)
              0 !== r[2 * a] ? ((t.heap[++t.heap_len] = h = a), (t.depth[a] = 0)) : (r[2 * a + 1] = 0);
            for (; t.heap_len < 2; )
              (r[2 * (n = t.heap[++t.heap_len] = h < 2 ? ++h : 0)] = 1),
                (t.depth[n] = 0),
                t.opt_len--,
                o && (t.static_len -= s[2 * n + 1]);
            for (e.max_code = h, a = t.heap_len >> 1; 1 <= a; a--) Y(t, r, a);
            for (
              n = l;
              (a = t.heap[1]),
                (t.heap[1] = t.heap[t.heap_len--]),
                Y(t, r, 1),
                (i = t.heap[1]),
                (t.heap[--t.heap_max] = a),
                (t.heap[--t.heap_max] = i),
                (r[2 * n] = r[2 * a] + r[2 * i]),
                (t.depth[n] = (t.depth[a] >= t.depth[i] ? t.depth[a] : t.depth[i]) + 1),
                (r[2 * a + 1] = r[2 * i + 1] = n),
                (t.heap[1] = n++),
                Y(t, r, 1),
                2 <= t.heap_len;

            );
            (t.heap[--t.heap_max] = t.heap[1]),
              (function (t, e) {
                var a,
                  i,
                  n,
                  r,
                  s,
                  o,
                  l = e.dyn_tree,
                  h = e.max_code,
                  d = e.stat_desc.static_tree,
                  f = e.stat_desc.has_stree,
                  _ = e.stat_desc.extra_bits,
                  u = e.stat_desc.extra_base,
                  c = e.stat_desc.max_length,
                  b = 0;
                for (r = 0; r <= m; r++) t.bl_count[r] = 0;
                for (l[2 * t.heap[t.heap_max] + 1] = 0, a = t.heap_max + 1; a < g; a++)
                  c < (r = l[2 * l[2 * (i = t.heap[a]) + 1] + 1] + 1) && ((r = c), b++),
                    (l[2 * i + 1] = r),
                    h < i ||
                      (t.bl_count[r]++,
                      (s = 0),
                      u <= i && (s = _[i - u]),
                      (o = l[2 * i]),
                      (t.opt_len += o * (r + s)),
                      f && (t.static_len += o * (d[2 * i + 1] + s)));
                if (0 !== b) {
                  do {
                    for (r = c - 1; 0 === t.bl_count[r]; ) r--;
                    t.bl_count[r]--, (t.bl_count[r + 1] += 2), t.bl_count[c]--, (b -= 2);
                  } while (0 < b);
                  for (r = c; 0 !== r; r--)
                    for (i = t.bl_count[r]; 0 !== i; )
                      h < (n = t.heap[--a]) ||
                        (l[2 * n + 1] !== r && ((t.opt_len += (r - l[2 * n + 1]) * l[2 * n]), (l[2 * n + 1] = r)), i--);
                }
              })(t, e),
              j(r, h, t.bl_count);
          }
          function X(t, e, a) {
            var i,
              n,
              r = -1,
              s = e[1],
              o = 0,
              l = 7,
              h = 4;
            for (0 === s && ((l = 138), (h = 3)), e[2 * (a + 1) + 1] = 65535, i = 0; i <= a; i++)
              (n = s),
                (s = e[2 * (i + 1) + 1]),
                (++o < l && n === s) ||
                  (o < h
                    ? (t.bl_tree[2 * n] += o)
                    : 0 !== n
                    ? (n !== r && t.bl_tree[2 * n]++, t.bl_tree[2 * p]++)
                    : o <= 10
                    ? t.bl_tree[2 * v]++
                    : t.bl_tree[2 * k]++,
                  (r = n),
                  (o = 0) === s ? ((l = 138), (h = 3)) : n === s ? ((l = 6), (h = 3)) : ((l = 7), (h = 4)));
          }
          function W(t, e, a) {
            var i,
              n,
              r = -1,
              s = e[1],
              o = 0,
              l = 7,
              h = 4;
            for (0 === s && ((l = 138), (h = 3)), i = 0; i <= a; i++)
              if (((n = s), (s = e[2 * (i + 1) + 1]), !(++o < l && n === s))) {
                if (o < h) for (; L(t, n, t.bl_tree), 0 != --o; );
                else
                  0 !== n
                    ? (n !== r && (L(t, n, t.bl_tree), o--), L(t, p, t.bl_tree), F(t, o - 3, 2))
                    : o <= 10
                    ? (L(t, v, t.bl_tree), F(t, o - 3, 3))
                    : (L(t, k, t.bl_tree), F(t, o - 11, 7));
                (r = n), (o = 0) === s ? ((l = 138), (h = 3)) : n === s ? ((l = 6), (h = 3)) : ((l = 7), (h = 4));
              }
          }
          i(D);
          var J = !1;
          function Q(t, e, a, i) {
            var n, r, s, o;
            F(t, (d << 1) + (i ? 1 : 0), 3),
              (r = e),
              (s = a),
              (o = !0),
              M((n = t)),
              o && (T(n, s), T(n, ~s)),
              l.arraySet(n.pending_buf, n.window, r, s, n.pending),
              (n.pending += s);
          }
          (a._tr_init = function (t) {
            J ||
              ((function () {
                var t,
                  e,
                  a,
                  i,
                  n,
                  r = new Array(m + 1);
                for (i = a = 0; i < s - 1; i++) for (R[i] = a, t = 0; t < 1 << y[i]; t++) Z[a++] = i;
                for (Z[a - 1] = i, i = n = 0; i < 16; i++) for (D[i] = n, t = 0; t < 1 << x[i]; t++) A[n++] = i;
                for (n >>= 7; i < u; i++) for (D[i] = n << 7, t = 0; t < 1 << (x[i] - 7); t++) A[256 + n++] = i;
                for (e = 0; e <= m; e++) r[e] = 0;
                for (t = 0; t <= 143; ) (S[2 * t + 1] = 8), t++, r[8]++;
                for (; t <= 255; ) (S[2 * t + 1] = 9), t++, r[9]++;
                for (; t <= 279; ) (S[2 * t + 1] = 7), t++, r[7]++;
                for (; t <= 287; ) (S[2 * t + 1] = 8), t++, r[8]++;
                for (j(S, _ + 1, r), t = 0; t < u; t++) (E[2 * t + 1] = 5), (E[2 * t] = H(t, 5));
                (C = new I(S, y, f + 1, _, m)), (N = new I(E, x, 0, u, m)), (O = new I(new Array(0), z, 0, c, b));
              })(),
              (J = !0)),
              (t.l_desc = new r(t.dyn_ltree, C)),
              (t.d_desc = new r(t.dyn_dtree, N)),
              (t.bl_desc = new r(t.bl_tree, O)),
              (t.bi_buf = 0),
              (t.bi_valid = 0),
              K(t);
          }),
            (a._tr_stored_block = Q),
            (a._tr_flush_block = function (t, e, a, i) {
              var n,
                r,
                s = 0;
              0 < t.level
                ? (2 === t.strm.data_type &&
                    (t.strm.data_type = (function (t) {
                      var e,
                        a = 4093624447;
                      for (e = 0; e <= 31; e++, a >>>= 1) if (1 & a && 0 !== t.dyn_ltree[2 * e]) return o;
                      if (0 !== t.dyn_ltree[18] || 0 !== t.dyn_ltree[20] || 0 !== t.dyn_ltree[26]) return h;
                      for (e = 32; e < f; e++) if (0 !== t.dyn_ltree[2 * e]) return h;
                      return o;
                    })(t)),
                  G(t, t.l_desc),
                  G(t, t.d_desc),
                  (s = (function (t) {
                    var e;
                    for (
                      X(t, t.dyn_ltree, t.l_desc.max_code),
                        X(t, t.dyn_dtree, t.d_desc.max_code),
                        G(t, t.bl_desc),
                        e = c - 1;
                      3 <= e && 0 === t.bl_tree[2 * B[e] + 1];
                      e--
                    );
                    return (t.opt_len += 3 * (e + 1) + 5 + 5 + 4), e;
                  })(t)),
                  (n = (t.opt_len + 3 + 7) >>> 3),
                  (r = (t.static_len + 3 + 7) >>> 3) <= n && (n = r))
                : (n = r = a + 5),
                a + 4 <= n && -1 !== e
                  ? Q(t, e, a, i)
                  : 4 === t.strategy || r === n
                  ? (F(t, 2 + (i ? 1 : 0), 3), q(t, S, E))
                  : (F(t, 4 + (i ? 1 : 0), 3),
                    (function (t, e, a, i) {
                      var n;
                      for (F(t, e - 257, 5), F(t, a - 1, 5), F(t, i - 4, 4), n = 0; n < i; n++)
                        F(t, t.bl_tree[2 * B[n] + 1], 3);
                      W(t, t.dyn_ltree, e - 1), W(t, t.dyn_dtree, a - 1);
                    })(t, t.l_desc.max_code + 1, t.d_desc.max_code + 1, s + 1),
                    q(t, t.dyn_ltree, t.dyn_dtree)),
                K(t),
                i && M(t);
            }),
            (a._tr_tally = function (t, e, a) {
              return (
                (t.pending_buf[t.d_buf + 2 * t.last_lit] = (e >>> 8) & 255),
                (t.pending_buf[t.d_buf + 2 * t.last_lit + 1] = 255 & e),
                (t.pending_buf[t.l_buf + t.last_lit] = 255 & a),
                t.last_lit++,
                0 === e
                  ? t.dyn_ltree[2 * a]++
                  : (t.matches++, e--, t.dyn_ltree[2 * (Z[a] + f + 1)]++, t.dyn_dtree[2 * U(e)]++),
                t.last_lit === t.lit_bufsize - 1
              );
            }),
            (a._tr_align = function (t) {
              var e;
              F(t, 2, 3),
                L(t, w, S),
                16 === (e = t).bi_valid
                  ? (T(e, e.bi_buf), (e.bi_buf = 0), (e.bi_valid = 0))
                  : 8 <= e.bi_valid &&
                    ((e.pending_buf[e.pending++] = 255 & e.bi_buf), (e.bi_buf >>= 8), (e.bi_valid -= 8));
            });
        },
        { '../utils/common': 3 },
      ],
      '15': [
        function (t, e, a) {
          'use strict';
          e.exports = function () {
            (this.input = null),
              (this.next_in = 0),
              (this.avail_in = 0),
              (this.total_in = 0),
              (this.output = null),
              (this.next_out = 0),
              (this.avail_out = 0),
              (this.total_out = 0),
              (this.msg = ''),
              (this.state = null),
              (this.data_type = 2),
              (this.adler = 0);
          };
        },
        {},
      ],
      '/': [
        function (t, e, a) {
          'use strict';
          var i = {};
          (0, t('./lib/utils/common').assign)(i, t('./lib/deflate'), t('./lib/inflate'), t('./lib/zlib/constants')),
            (e.exports = i);
        },
        { './lib/deflate': 1, './lib/inflate': 2, './lib/utils/common': 3, './lib/zlib/constants': 6 },
      ],
    },
    {},
    []
  )('/');
});
/**
 *
 *  Base64 encode / decode
 *  http://www.webtoolkit.info/
 *
 **/

var Base64 = {
  // private property
  _keyStr: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',

  // public method for encoding
  encode: function (input, binary) {
    binary = binary != null ? binary : false;
    var output = '';
    var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
    var i = 0;

    if (!binary) {
      input = Base64._utf8_encode(input);
    }

    while (i < input.length) {
      chr1 = input.charCodeAt(i++);
      chr2 = input.charCodeAt(i++);
      chr3 = input.charCodeAt(i++);

      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;

      if (isNaN(chr2)) {
        enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
        enc4 = 64;
      }

      output =
        output +
        this._keyStr.charAt(enc1) +
        this._keyStr.charAt(enc2) +
        this._keyStr.charAt(enc3) +
        this._keyStr.charAt(enc4);
    }

    return output;
  },

  // public method for decoding
  decode: function (input, binary) {
    binary = binary != null ? binary : false;
    var output = '';
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;

    input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');

    while (i < input.length) {
      enc1 = this._keyStr.indexOf(input.charAt(i++));
      enc2 = this._keyStr.indexOf(input.charAt(i++));
      enc3 = this._keyStr.indexOf(input.charAt(i++));
      enc4 = this._keyStr.indexOf(input.charAt(i++));

      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;

      output = output + String.fromCharCode(chr1);

      if (enc3 != 64) {
        output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
        output = output + String.fromCharCode(chr3);
      }
    }

    if (!binary) {
      output = Base64._utf8_decode(output);
    }

    return output;
  },

  // private method for UTF-8 encoding
  _utf8_encode: function (string) {
    string = string.replace(/\r\n/g, '\n');
    var utftext = '';

    for (var n = 0; n < string.length; n++) {
      var c = string.charCodeAt(n);

      if (c < 128) {
        utftext += String.fromCharCode(c);
      } else if (c > 127 && c < 2048) {
        utftext += String.fromCharCode((c >> 6) | 192);
        utftext += String.fromCharCode((c & 63) | 128);
      } else {
        utftext += String.fromCharCode((c >> 12) | 224);
        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
        utftext += String.fromCharCode((c & 63) | 128);
      }
    }

    return utftext;
  },

  // private method for UTF-8 decoding
  _utf8_decode: function (utftext) {
    var string = '';
    var i = 0;
    var c = (c1 = c2 = 0);

    while (i < utftext.length) {
      c = utftext.charCodeAt(i);

      if (c < 128) {
        string += String.fromCharCode(c);
        i++;
      } else if (c > 191 && c < 224) {
        c2 = utftext.charCodeAt(i + 1);
        string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
        i += 2;
      } else {
        c2 = utftext.charCodeAt(i + 1);
        c3 = utftext.charCodeAt(i + 2);
        string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
        i += 3;
      }
    }

    return string;
  },
}; // NOTE: Modified to support data URIs for images, ie. data:image/*
// Modified to allow "word-break: break-word" in styles. It is done by adding "break-word" which is at index 55 of J array J[55] to "cssLitGroup" of "word-break"
(function () {
  var c = void 0,
    n = !0,
    s = null,
    C = !1,
    J = [
      'aliceblue,antiquewhite,aqua,aquamarine,azure,beige,bisque,black,blanchedalmond,blue,blueviolet,brown,burlywood,cadetblue,chartreuse,chocolate,coral,cornflowerblue,cornsilk,crimson,cyan,darkblue,darkcyan,darkgoldenrod,darkgray,darkgreen,darkkhaki,darkmagenta,darkolivegreen,darkorange,darkorchid,darkred,darksalmon,darkseagreen,darkslateblue,darkslategray,darkturquoise,darkviolet,deeppink,deepskyblue,dimgray,dodgerblue,firebrick,floralwhite,forestgreen,fuchsia,gainsboro,ghostwhite,gold,goldenrod,gray,green,greenyellow,honeydew,hotpink,indianred,indigo,ivory,khaki,lavender,lavenderblush,lawngreen,lemonchiffon,lightblue,lightcoral,lightcyan,lightgoldenrodyellow,lightgreen,lightgrey,lightpink,lightsalmon,lightseagreen,lightskyblue,lightslategray,lightsteelblue,lightyellow,lime,limegreen,linen,magenta,maroon,mediumaquamarine,mediumblue,mediumorchid,mediumpurple,mediumseagreen,mediumslateblue,mediumspringgreen,mediumturquoise,mediumvioletred,midnightblue,mintcream,mistyrose,moccasin,navajowhite,navy,oldlace,olive,olivedrab,orange,orangered,orchid,palegoldenrod,palegreen,paleturquoise,palevioletred,papayawhip,peachpuff,peru,pink,plum,powderblue,purple,red,rosybrown,royalblue,saddlebrown,salmon,sandybrown,seagreen,seashell,sienna,silver,skyblue,slateblue,slategray,snow,springgreen,steelblue,tan,teal,thistle,tomato,transparent,turquoise,violet,wheat,white,whitesmoke,yellow,yellowgreen'.split(
        ','
      ),
      'all-scroll,col-resize,crosshair,default,e-resize,hand,help,move,n-resize,ne-resize,no-drop,not-allowed,nw-resize,pointer,progress,row-resize,s-resize,se-resize,sw-resize,text,vertical-text,w-resize,wait'.split(
        ','
      ),
      'armenian,decimal,decimal-leading-zero,disc,georgian,lower-alpha,lower-greek,lower-latin,lower-roman,square,upper-alpha,upper-latin,upper-roman'.split(
        ','
      ),
      '100,200,300,400,500,600,700,800,900,bold,bolder,lighter'.split(','),
      'block-level,inline-level,table-caption,table-cell,table-column,table-column-group,table-footer-group,table-header-group,table-row,table-row-group'.split(
        ','
      ),
      'condensed,expanded,extra-condensed,extra-expanded,narrower,semi-condensed,semi-expanded,ultra-condensed,ultra-expanded,wider'.split(
        ','
      ),
      'inherit,inline,inline-block,inline-box,inline-flex,inline-grid,inline-list-item,inline-stack,inline-table,run-in'.split(
        ','
      ),
      'behind,center-left,center-right,far-left,far-right,left-side,leftwards,right-side,rightwards'.split(','),
      'large,larger,small,smaller,x-large,x-small,xx-large,xx-small'.split(','),
      'dashed,dotted,double,groove,outset,ridge,solid'.split(','),
      'ease,ease-in,ease-in-out,ease-out,linear,step-end,step-start'.split(','),
      'at,closest-corner,closest-side,ellipse,farthest-corner,farthest-side'.split(','),
      'baseline,middle,sub,super,text-bottom,text-top'.split(','),
      'caption,icon,menu,message-box,small-caption,status-bar'.split(','),
      'fast,faster,slow,slower,x-fast,x-slow'.split(','),
      ['above', 'below', 'higher', 'level', 'lower'],
      ['cursive', 'fantasy', 'monospace', 'sans-serif', 'serif'],
      ['loud', 'silent', 'soft', 'x-loud', 'x-soft'],
      ['no-repeat', 'repeat-x', 'repeat-y', 'round', 'space'],
      ['blink', 'line-through', 'overline', 'underline'],
      ['block', 'flex', 'grid', 'table'],
      ['high', 'low', 'x-high', 'x-low'],
      ['nowrap', 'pre', 'pre-line', 'pre-wrap'],
      ['absolute', 'relative', 'static'],
      ['alternate', 'alternate-reverse', 'reverse'],
      ['border-box', 'content-box', 'padding-box'],
      ['capitalize', 'lowercase', 'uppercase'],
      ['child', 'female', 'male'],
      ['=', 'opacity'],
      ['backwards', 'forwards'],
      ['bidi-override', 'embed'],
      ['bottom', 'top'],
      ['break-all', 'keep-all'],
      ['clip', 'ellipsis'],
      ['contain', 'cover'],
      ['continuous', 'digits'],
      ['end', 'start'],
      ['flat', 'preserve-3d'],
      ['hide', 'show'],
      ['horizontal', 'vertical'],
      ['inside', 'outside'],
      ['italic', 'oblique'],
      ['left', 'right'],
      ['ltr', 'rtl'],
      ['no-content', 'no-display'],
      ['paused', 'running'],
      ['suppress', 'unrestricted'],
      ['thick', 'thin'],
      [','],
      ['/'],
      ['all'],
      ['always'],
      ['auto'],
      ['avoid'],
      ['both'],
      ['break-word'],
      ['center'],
      ['circle'],
      ['code'],
      ['collapse'],
      ['contents'],
      ['fixed'],
      ['hidden'],
      ['infinite'],
      ['inset'],
      ['invert'],
      ['justify'],
      ['list-item'],
      ['local'],
      ['medium'],
      ['mix'],
      ['none'],
      ['normal'],
      ['once'],
      ['repeat'],
      ['scroll'],
      ['separate'],
      ['small-caps'],
      ['spell-out'],
      ['to'],
      ['visible'],
    ],
    L = {
      'animation': {
        cssPropBits: 517,
        cssLitGroup: [J[10], J[24], J[29], J[45], J[48], J[54], J[63], J[71], J[72]],
        cssFns: ['cubic-bezier()', 'steps()'],
      },
      'animation-delay': { cssPropBits: 5, cssLitGroup: [J[48]], cssFns: [] },
      'animation-direction': { cssPropBits: 0, cssLitGroup: [J[24], J[48], J[72]], cssFns: [] },
      'animation-duration': 'animation-delay',
      'animation-fill-mode': { cssPropBits: 0, cssLitGroup: [J[29], J[48], J[54], J[71]], cssFns: [] },
      'animation-iteration-count': { cssPropBits: 5, cssLitGroup: [J[48], J[63]], cssFns: [] },
      'animation-name': { cssPropBits: 512, cssLitGroup: [J[48], J[71]], cssFns: [] },
      'animation-play-state': { cssPropBits: 0, cssLitGroup: [J[45], J[48]], cssFns: [] },
      'animation-timing-function': {
        cssPropBits: 0,
        cssLitGroup: [J[10], J[48]],
        cssFns: ['cubic-bezier()', 'steps()'],
      },
      'appearance': { cssPropBits: 0, cssLitGroup: [J[71]], cssFns: [] },
      'azimuth': { cssPropBits: 5, cssLitGroup: [J[7], J[42], J[56]], cssFns: [] },
      'backface-visibility': { cssPropBits: 0, cssLitGroup: [J[59], J[62], J[80]], cssFns: [] },
      'background': {
        cssPropBits: 23,
        cssLitGroup: [
          J[0],
          J[18],
          J[25],
          J[31],
          J[34],
          J[42],
          J[48],
          J[49],
          J[52],
          J[56],
          J[61],
          J[68],
          J[71],
          J[74],
          J[75],
        ],
        cssFns:
          'image(),linear-gradient(),radial-gradient(),repeating-linear-gradient(),repeating-radial-gradient(),rgb(),rgba()'.split(
            ','
          ),
      },
      'background-attachment': { cssPropBits: 0, cssLitGroup: [J[48], J[61], J[68], J[75]], cssFns: [] },
      'background-color': { cssPropBits: 2, cssLitGroup: [J[0]], cssFns: ['rgb()', 'rgba()'] },
      'background-image': {
        cssPropBits: 16,
        cssLitGroup: [J[48], J[71]],
        cssFns: [
          'image()',
          'linear-gradient()',
          'radial-gradient()',
          'repeating-linear-gradient()',
          'repeating-radial-gradient()',
        ],
      },
      'background-position': { cssPropBits: 5, cssLitGroup: [J[31], J[42], J[48], J[56]], cssFns: [] },
      'background-repeat': { cssPropBits: 0, cssLitGroup: [J[18], J[48], J[74]], cssFns: [] },
      'background-size': { cssPropBits: 5, cssLitGroup: [J[34], J[48], J[52]], cssFns: [] },
      'border': {
        cssPropBits: 7,
        cssLitGroup: [J[0], J[9], J[47], J[62], J[64], J[69], J[71]],
        cssFns: ['rgb()', 'rgba()'],
      },
      'border-bottom': 'border',
      'border-bottom-color': 'background-color',
      'border-bottom-left-radius': { cssPropBits: 5, cssFns: [] },
      'border-bottom-right-radius': 'border-bottom-left-radius',
      'border-bottom-style': { cssPropBits: 0, cssLitGroup: [J[9], J[62], J[64], J[71]], cssFns: [] },
      'border-bottom-width': { cssPropBits: 5, cssLitGroup: [J[47], J[69]], cssFns: [] },
      'border-collapse': { cssPropBits: 0, cssLitGroup: [J[59], J[76]], cssFns: [] },
      'border-color': 'background-color',
      'border-left': 'border',
      'border-left-color': 'background-color',
      'border-left-style': 'border-bottom-style',
      'border-left-width': 'border-bottom-width',
      'border-radius': { cssPropBits: 5, cssLitGroup: [J[49]], cssFns: [] },
      'border-right': 'border',
      'border-right-color': 'background-color',
      'border-right-style': 'border-bottom-style',
      'border-right-width': 'border-bottom-width',
      'border-spacing': 'border-bottom-left-radius',
      'border-style': 'border-bottom-style',
      'border-top': 'border',
      'border-top-color': 'background-color',
      'border-top-left-radius': 'border-bottom-left-radius',
      'border-top-right-radius': 'border-bottom-left-radius',
      'border-top-style': 'border-bottom-style',
      'border-top-width': 'border-bottom-width',
      'border-width': 'border-bottom-width',
      'bottom': { cssPropBits: 5, cssLitGroup: [J[52]], cssFns: [] },
      'box': { cssPropBits: 0, cssLitGroup: [J[60], J[71], J[72]], cssFns: [] },
      'box-shadow': { cssPropBits: 7, cssLitGroup: [J[0], J[48], J[64], J[71]], cssFns: ['rgb()', 'rgba()'] },
      'box-sizing': { cssPropBits: 0, cssLitGroup: [J[25]], cssFns: [] },
      'caption-side': { cssPropBits: 0, cssLitGroup: [J[31]], cssFns: [] },
      'clear': { cssPropBits: 0, cssLitGroup: [J[42], J[54], J[71]], cssFns: [] },
      'clip': { cssPropBits: 0, cssLitGroup: [J[52]], cssFns: ['rect()'] },
      'color': 'background-color',
      'content': { cssPropBits: 8, cssLitGroup: [J[71], J[72]], cssFns: [] },
      'cue': { cssPropBits: 16, cssLitGroup: [J[71]], cssFns: [] },
      'cue-after': 'cue',
      'cue-before': 'cue',
      'cursor': { cssPropBits: 16, cssLitGroup: [J[1], J[48], J[52]], cssFns: [] },
      'direction': { cssPropBits: 0, cssLitGroup: [J[43]], cssFns: [] },
      'display': { cssPropBits: 0, cssLitGroup: [J[4], J[6], J[20], J[52], J[67], J[71]], cssFns: [] },
      'display-extras': { cssPropBits: 0, cssLitGroup: [J[67], J[71]], cssFns: [] },
      'display-inside': { cssPropBits: 0, cssLitGroup: [J[20], J[52]], cssFns: [] },
      'display-outside': { cssPropBits: 0, cssLitGroup: [J[4], J[71]], cssFns: [] },
      'elevation': { cssPropBits: 5, cssLitGroup: [J[15]], cssFns: [] },
      'empty-cells': { cssPropBits: 0, cssLitGroup: [J[38]], cssFns: [] },
      'filter': { cssPropBits: 0, cssFns: ['alpha()'] },
      'float': { cssPropBits: 0, cssLitGroup: [J[42], J[71]], cssFns: [] },
      'font': {
        cssPropBits: 73,
        cssLitGroup: [J[3], J[8], J[13], J[16], J[41], J[48], J[49], J[69], J[72], J[77]],
        cssFns: [],
      },
      'font-family': { cssPropBits: 72, cssLitGroup: [J[16], J[48]], cssFns: [] },
      'font-size': { cssPropBits: 1, cssLitGroup: [J[8], J[69]], cssFns: [] },
      'font-stretch': { cssPropBits: 0, cssLitGroup: [J[5], J[72]], cssFns: [] },
      'font-style': { cssPropBits: 0, cssLitGroup: [J[41], J[72]], cssFns: [] },
      'font-variant': { cssPropBits: 0, cssLitGroup: [J[72], J[77]], cssFns: [] },
      'font-weight': { cssPropBits: 0, cssLitGroup: [J[3], J[72]], cssFns: [] },
      'height': 'bottom',
      'left': 'bottom',
      'letter-spacing': { cssPropBits: 5, cssLitGroup: [J[72]], cssFns: [] },
      'line-height': { cssPropBits: 1, cssLitGroup: [J[72]], cssFns: [] },
      'list-style': {
        cssPropBits: 16,
        cssLitGroup: [J[2], J[40], J[57], J[71]],
        cssFns: [
          'image()',
          'linear-gradient()',
          'radial-gradient()',
          'repeating-linear-gradient()',
          'repeating-radial-gradient()',
        ],
      },
      'list-style-image': {
        cssPropBits: 16,
        cssLitGroup: [J[71]],
        cssFns: [
          'image()',
          'linear-gradient()',
          'radial-gradient()',
          'repeating-linear-gradient()',
          'repeating-radial-gradient()',
        ],
      },
      'list-style-position': { cssPropBits: 0, cssLitGroup: [J[40]], cssFns: [] },
      'list-style-type': { cssPropBits: 0, cssLitGroup: [J[2], J[57], J[71]], cssFns: [] },
      'margin': 'bottom',
      'margin-bottom': 'bottom',
      'margin-left': 'bottom',
      'margin-right': 'bottom',
      'margin-top': 'bottom',
      'max-height': { cssPropBits: 1, cssLitGroup: [J[52], J[71]], cssFns: [] },
      'max-width': 'max-height',
      'min-height': { cssPropBits: 1, cssLitGroup: [J[52]], cssFns: [] },
      'min-width': 'min-height',
      'opacity': { cssPropBits: 1, cssFns: [] },
      'outline': {
        cssPropBits: 7,
        cssLitGroup: [J[0], J[9], J[47], J[62], J[64], J[65], J[69], J[71]],
        cssFns: ['rgb()', 'rgba()'],
      },
      'outline-color': { cssPropBits: 2, cssLitGroup: [J[0], J[65]], cssFns: ['rgb()', 'rgba()'] },
      'outline-style': 'border-bottom-style',
      'outline-width': 'border-bottom-width',
      'overflow': { cssPropBits: 0, cssLitGroup: [J[52], J[62], J[75], J[80]], cssFns: [] },
      'overflow-wrap': { cssPropBits: 0, cssLitGroup: [J[55], J[72]], cssFns: [] },
      'overflow-x': { cssPropBits: 0, cssLitGroup: [J[44], J[52], J[62], J[75], J[80]], cssFns: [] },
      'overflow-y': 'overflow-x',
      'padding': 'opacity',
      'padding-bottom': 'opacity',
      'padding-left': 'opacity',
      'padding-right': 'opacity',
      'padding-top': 'opacity',
      'page-break-after': { cssPropBits: 0, cssLitGroup: [J[42], J[51], J[52], J[53]], cssFns: [] },
      'page-break-before': 'page-break-after',
      'page-break-inside': { cssPropBits: 0, cssLitGroup: [J[52], J[53]], cssFns: [] },
      'pause': 'border-bottom-left-radius',
      'pause-after': 'border-bottom-left-radius',
      'pause-before': 'border-bottom-left-radius',
      'perspective': { cssPropBits: 5, cssLitGroup: [J[71]], cssFns: [] },
      'perspective-origin': { cssPropBits: 5, cssLitGroup: [J[31], J[42], J[56]], cssFns: [] },
      'pitch': { cssPropBits: 5, cssLitGroup: [J[21], J[69]], cssFns: [] },
      'pitch-range': 'border-bottom-left-radius',
      'play-during': { cssPropBits: 16, cssLitGroup: [J[52], J[70], J[71], J[74]], cssFns: [] },
      'position': { cssPropBits: 0, cssLitGroup: [J[23]], cssFns: [] },
      'quotes': { cssPropBits: 8, cssLitGroup: [J[71]], cssFns: [] },
      'resize': { cssPropBits: 0, cssLitGroup: [J[39], J[54], J[71]], cssFns: [] },
      'richness': 'border-bottom-left-radius',
      'right': 'bottom',
      'speak': { cssPropBits: 0, cssLitGroup: [J[71], J[72], J[78]], cssFns: [] },
      'speak-header': { cssPropBits: 0, cssLitGroup: [J[51], J[73]], cssFns: [] },
      'speak-numeral': { cssPropBits: 0, cssLitGroup: [J[35]], cssFns: [] },
      'speak-punctuation': { cssPropBits: 0, cssLitGroup: [J[58], J[71]], cssFns: [] },
      'speech-rate': { cssPropBits: 5, cssLitGroup: [J[14], J[69]], cssFns: [] },
      'stress': 'border-bottom-left-radius',
      'table-layout': { cssPropBits: 0, cssLitGroup: [J[52], J[61]], cssFns: [] },
      'text-align': { cssPropBits: 0, cssLitGroup: [J[42], J[56], J[66]], cssFns: [] },
      'text-decoration': { cssPropBits: 0, cssLitGroup: [J[19], J[71]], cssFns: [] },
      'text-indent': 'border-bottom-left-radius',
      'text-overflow': { cssPropBits: 8, cssLitGroup: [J[33]], cssFns: [] },
      'text-shadow': 'box-shadow',
      'text-transform': { cssPropBits: 0, cssLitGroup: [J[26], J[71]], cssFns: [] },
      'text-wrap': { cssPropBits: 0, cssLitGroup: [J[46], J[71], J[72]], cssFns: [] },
      'top': 'bottom',
      'transform': {
        cssPropBits: 0,
        cssLitGroup: [J[71]],
        cssFns:
          'matrix(),perspective(),rotate(),rotate3d(),rotatex(),rotatey(),rotatez(),scale(),scale3d(),scalex(),scaley(),scalez(),skew(),skewx(),skewy(),translate(),translate3d(),translatex(),translatey(),translatez()'.split(
            ','
          ),
      },
      'transform-origin': 'perspective-origin',
      'transform-style': { cssPropBits: 0, cssLitGroup: [J[37]], cssFns: [] },
      'transition': {
        cssPropBits: 1029,
        cssLitGroup: [J[10], J[48], J[50], J[71]],
        cssFns: ['cubic-bezier()', 'steps()'],
      },
      'transition-delay': 'animation-delay',
      'transition-duration': 'animation-delay',
      'transition-property': { cssPropBits: 1024, cssLitGroup: [J[48], J[50]], cssFns: [] },
      'transition-timing-function': 'animation-timing-function',
      'unicode-bidi': { cssPropBits: 0, cssLitGroup: [J[30], J[72]], cssFns: [] },
      'vertical-align': { cssPropBits: 5, cssLitGroup: [J[12], J[31]], cssFns: [] },
      'visibility': 'backface-visibility',
      'voice-family': { cssPropBits: 8, cssLitGroup: [J[27], J[48]], cssFns: [] },
      'volume': { cssPropBits: 1, cssLitGroup: [J[17], J[69]], cssFns: [] },
      'white-space': { cssPropBits: 0, cssLitGroup: [J[22], J[72]], cssFns: [] },
      'width': 'min-height',
      'word-break': { cssPropBits: 0, cssLitGroup: [J[32], J[72], J[55]], cssFns: [] },
      'word-spacing': 'letter-spacing',
      'word-wrap': 'overflow-wrap',
      'z-index': 'bottom',
      'zoom': 'line-height',
      'cubic-bezier()': 'animation-delay',
      'steps()': { cssPropBits: 5, cssLitGroup: [J[36], J[48]], cssFns: [] },
      'image()': { cssPropBits: 18, cssLitGroup: [J[0], J[48]], cssFns: ['rgb()', 'rgba()'] },
      'linear-gradient()': {
        cssPropBits: 7,
        cssLitGroup: [J[0], J[31], J[42], J[48], J[79]],
        cssFns: ['rgb()', 'rgba()'],
      },
      'radial-gradient()': {
        cssPropBits: 7,
        cssLitGroup: [J[0], J[11], J[31], J[42], J[48], J[56], J[57]],
        cssFns: ['rgb()', 'rgba()'],
      },
      'repeating-linear-gradient()': 'linear-gradient()',
      'repeating-radial-gradient()': 'radial-gradient()',
      'rgb()': { cssPropBits: 1, cssLitGroup: [J[48]], cssFns: [] },
      'rgba()': 'rgb()',
      'rect()': { cssPropBits: 5, cssLitGroup: [J[48], J[52]], cssFns: [] },
      'alpha()': { cssPropBits: 1, cssLitGroup: [J[28]], cssFns: [] },
      'matrix()': 'animation-delay',
      'perspective()': 'border-bottom-left-radius',
      'rotate()': 'border-bottom-left-radius',
      'rotate3d()': 'animation-delay',
      'rotatex()': 'border-bottom-left-radius',
      'rotatey()': 'border-bottom-left-radius',
      'rotatez()': 'border-bottom-left-radius',
      'scale()': 'animation-delay',
      'scale3d()': 'animation-delay',
      'scalex()': 'border-bottom-left-radius',
      'scaley()': 'border-bottom-left-radius',
      'scalez()': 'border-bottom-left-radius',
      'skew()': 'animation-delay',
      'skewx()': 'border-bottom-left-radius',
      'skewy()': 'border-bottom-left-radius',
      'translate()': 'animation-delay',
      'translate3d()': 'animation-delay',
      'translatex()': 'border-bottom-left-radius',
      'translatey()': 'border-bottom-left-radius',
      'translatez()': 'border-bottom-left-radius',
    },
    O;
  for (O in L) 'string' === typeof L[O] && Object.hasOwnProperty.call(L, O) && (L[O] = L[L[O]]);
  'undefined' !== typeof window && (window.cssSchema = L);
  var U, X;
  (function () {
    function g(a) {
      var f = parseInt(a.substring(1), 16);
      return 65535 < f
        ? ((f -= 65536), String.fromCharCode(55296 + (f >> 10), 56320 + (f & 1023)))
        : f == f
        ? String.fromCharCode(f)
        : ' ' > a[1]
        ? ''
        : a[1];
    }
    function w(a, f) {
      return '"' + a.replace(/[\u0000-\u001f\\\"<>]/g, f) + '"';
    }
    function M(a) {
      return E[a] || (E[a] = '\\' + a.charCodeAt(0).toString(16) + ' ');
    }
    function x(a) {
      return e[a] || (e[a] = ('\u0010' > a ? '%0' : '%') + a.charCodeAt(0).toString(16));
    }
    var E = { '\\': '\\\\' },
      e = { '\\': '%5c' },
      v = RegExp(
        '\\uFEFF|U[+][0-9A-F?]{1,6}(?:-[0-9A-F]{1,6})?|url[(][\\t\\n\\f ]*(?:"(?:\'|[^\'"\\n\\f\\\\]|\\\\[\\s\\S])*"|\'(?:"|[^\'"\\n\\f\\\\]|\\\\[\\s\\S])*\'|(?:[\\t\\x21\\x23-\\x26\\x28-\\x5b\\x5d-\\x7e]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))*)[\\t\\n\\f ]*[)]|(?!url[(])-?(?:[a-zA-Z_]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))(?:[a-zA-Z0-9_-]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))*[(]|(?:@?-?(?:[a-zA-Z_]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))|#)(?:[a-zA-Z0-9_-]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))*|"(?:\'|[^\'"\\n\\f\\\\]|\\\\[\\s\\S])*"|\'(?:"|[^\'"\\n\\f\\\\]|\\\\[\\s\\S])*\'|[-+]?(?:[0-9]+(?:[.][0-9]+)?|[.][0-9]+)(?:%|-?(?:[a-zA-Z_]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))(?:[a-zA-Z0-9_-]|[\\u0080-\\ud7ff\\ue000-\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]|\\\\(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff]))*)?|<!--|-->|[\\t\\n\\f ]+|/(?:[*][^*]*[*]+(?:[^/][^*]*[*]+)*/|/[^\\n\\f]*)|[~|^$*]=|[^"\'\\\\/]|/(?![/*])',
        'gi'
      ),
      b = RegExp(
        '\\\\(?:(?:[0-9a-fA-F]{1,6}[\\t\\n\\f ]?|[\\u0020-\\u007e\\u0080-\\ud7ff\\ue000\\ufffd]|[\\ud800-\\udbff][\\udc00-\\udfff])|[\\n\\f])',
        'g'
      ),
      a = RegExp('^url\\([\\t\\n\\f ]*["\']?|["\']?[\\t\\n\\f ]*\\)$', 'gi');
    X = function (a) {
      return a.replace(b, g);
    };
    U = function (b) {
      for (var b = ('' + b).replace(/\r\n?/g, '\n').match(v) || [], f = 0, h = ' ', d = 0, y = b.length; d < y; ++d) {
        var l = X(b[d]),
          V = l.length,
          g = l.charCodeAt(0),
          l =
            34 == g || 39 == g
              ? w(l.substring(1, V - 1), M)
              : (47 == g && 1 < V) || '\\' == l || '-->' == l || '<!--' == l || '\ufeff' == l || 32 >= g
              ? ' '
              : /url\(/i.test(l)
              ? 'url(' + w(l.replace(a, ''), x) + ')'
              : l;
        if (h != l || ' ' != l) b[f++] = h = l;
      }
      b.length = f;
      return b;
    };
  })();
  'undefined' !== typeof window && ((window.lexCss = U), (window.decodeCss = X));
  var Y = (function () {
    function g(d) {
      d = ('' + d).match(k);
      return !d ? s : new e(v(d[1]), v(d[2]), v(d[3]), v(d[4]), v(d[5]), v(d[6]), v(d[7]));
    }
    function w(d, a) {
      return 'string' == typeof d ? encodeURI(d).replace(a, M) : s;
    }
    function M(d) {
      d = d.charCodeAt(0);
      return '%' + '0123456789ABCDEF'.charAt((d >> 4) & 15) + '0123456789ABCDEF'.charAt(d & 15);
    }
    function x(d) {
      if (d === s) return s;
      for (
        var d = d.replace(/(^|\/)\.(?:\/|$)/g, '$1').replace(/\/{2,}/g, '/'), a = b, h;
        (h = d.replace(a, '$1')) != d;
        d = h
      );
      return d;
    }
    function E(d, h) {
      var b = d.T(),
        f = h.K();
      f ? b.ga(h.j) : (f = h.X());
      f ? b.da(h.n) : (f = h.Y());
      f ? b.ea(h.k) : (f = h.$());
      var g = h.g,
        k = x(g);
      if (f) b.ca(h.V()), (k = k && k.replace(a, ''));
      else if ((f = !!g)) {
        if (47 !== k.charCodeAt(0))
          var k = x(b.g || '').replace(a, ''),
            e = k.lastIndexOf('/') + 1,
            k = x((e ? k.substring(0, e) : '') + x(g)).replace(a, '');
      } else (k = k && k.replace(a, '')), k !== g && b.G(k);
      f ? b.G(k) : (f = h.aa());
      f ? b.O(h.l) : (f = h.Z());
      f && b.fa(h.o);
      return b;
    }
    function e(d, a, h, f, b, g, k) {
      this.j = d;
      this.n = a;
      this.k = h;
      this.h = f;
      this.g = b;
      this.l = g;
      this.o = k;
    }
    function v(d) {
      return 'string' == typeof d && 0 < d.length ? d : s;
    }
    var b = RegExp(/(\/|^)(?:[^./][^/]*|\.{2,}(?:[^./][^/]*)|\.{3,}[^/]*)\/\.\.(?:\/|$)/),
      a = /^(?:\.\.\/)*(?:\.\.$)?/;
    e.prototype.toString = function () {
      var d = [];
      s !== this.j && d.push(this.j, ':');
      s !== this.k &&
        (d.push('//'),
        s !== this.n && d.push(this.n, '@'),
        d.push(this.k),
        s !== this.h && d.push(':', this.h.toString()));
      s !== this.g && d.push(this.g);
      s !== this.l && d.push('?', this.l);
      s !== this.o && d.push('#', this.o);
      return d.join('');
    };
    e.prototype.T = function () {
      return new e(this.j, this.n, this.k, this.h, this.g, this.l, this.o);
    };
    e.prototype.W = function () {
      return this.j && decodeURIComponent(this.j).toLowerCase();
    };
    e.prototype.ga = function (d) {
      this.j = d ? d : s;
    };
    e.prototype.K = function () {
      return s !== this.j;
    };
    e.prototype.da = function (d) {
      this.n = d ? d : s;
    };
    e.prototype.X = function () {
      return s !== this.n;
    };
    e.prototype.ea = function (d) {
      this.k = d ? d : s;
      this.G(this.g);
    };
    e.prototype.Y = function () {
      return s !== this.k;
    };
    e.prototype.V = function () {
      return this.h && decodeURIComponent(this.h);
    };
    e.prototype.ca = function (d) {
      if (d) {
        d = Number(d);
        if (d !== (d & 65535)) throw Error('Bad port number ' + d);
        this.h = '' + d;
      } else this.h = s;
    };
    e.prototype.$ = function () {
      return s !== this.h;
    };
    e.prototype.U = function () {
      return this.g && decodeURIComponent(this.g);
    };
    e.prototype.G = function (d) {
      d ? ((d = '' + d), (this.g = !this.k || /^\//.test(d) ? d : '/' + d)) : (this.g = s);
    };
    e.prototype.O = function (d) {
      this.l = d ? d : s;
    };
    e.prototype.aa = function () {
      return s !== this.l;
    };
    e.prototype.ba = function (d) {
      if (
        'object' === typeof d &&
        !(d instanceof Array) &&
        (d instanceof Object || '[object Array]' !== Object.prototype.toString.call(d))
      ) {
        var a = [],
          h = -1,
          f;
        for (f in d) {
          var b = d[f];
          'string' === typeof b && ((a[++h] = f), (a[++h] = b));
        }
        d = a;
      }
      for (var a = [], h = '', g = 0; g < d.length; )
        (f = d[g++]),
          (b = d[g++]),
          a.push(h, encodeURIComponent(f.toString())),
          (h = '&'),
          b && a.push('=', encodeURIComponent(b.toString()));
      this.l = a.join('');
    };
    e.prototype.fa = function (d) {
      this.o = d ? d : s;
    };
    e.prototype.Z = function () {
      return s !== this.o;
    };
    var k = /^(?:([^:/?#]+):)?(?:\/\/(?:([^/?#]*)@)?([^/?#:@]*)(?::([0-9]+))?)?([^?#]+)?(?:\?([^#]*))?(?:#(.*))?$/,
      f = /[#\/\?@]/g,
      h = /[\#\?]/g;
    e.parse = g;
    e.create = function (d, a, b, g, k, Q, N) {
      d = new e(
        w(d, f),
        w(a, f),
        'string' == typeof b ? encodeURIComponent(b) : s,
        0 < g ? g.toString() : s,
        w(k, h),
        s,
        'string' == typeof N ? encodeURIComponent(N) : s
      );
      Q && ('string' === typeof Q ? d.O(Q.replace(/[^?&=0-9A-Za-z_\-~.%]/g, M)) : d.ba(Q));
      return d;
    };
    e.N = E;
    e.ma = x;
    e.ha = {
      ua: function (d) {
        return /\.html$/.test(g(d).U()) ? 'text/html' : 'application/javascript';
      },
      N: function (d, a) {
        return d ? E(g(d), g(a)).toString() : '' + a;
      },
    };
    return e;
  })();
  'undefined' !== typeof window && (window.URI = Y);
  var aa = c,
    ba = c,
    da = c,
    Z = c;
  (function () {
    function g(a) {
      return 'string' === typeof a ? 'url("' + a.replace(e, w) + '")' : 'url("about:blank")';
    }
    function w(a) {
      return v[a];
    }
    function M(a, d) {
      return a ? Y.ha.N(a, d) : d;
    }
    function x(h, d, f) {
      if (!f) return s;
      var g = ('' + h).match(b);
      return g && (!g[1] || a.test(g[1])) ? f(h, d) : s;
    }
    function E(a) {
      return a.replace(/^-(?:apple|css|epub|khtml|moz|mso?|o|rim|wap|webkit|xv)-(?=[a-z])/, '');
    }
    var e = /[\n\f\r\"\'()*<>]/g,
      v = {
        '\n': '%0a',
        '\u000c': '%0c',
        '\r': '%0d',
        '"': '%22',
        "'": '%27',
        '(': '%28',
        ')': '%29',
        '*': '%2a',
        '<': '%3c',
        '>': '%3e',
      },
      b = /^(?:([^:/?# ]+):)?/,
      a = /^(?:https?|mailto|data)$/i;
    aa = (function () {
      var a = {};
      return function y(f, b, k, e, N) {
        var f = E(f),
          u = L[f];
        if (!u || 'object' !== typeof u) b.length = 0;
        else {
          for (var i = u.cssPropBits, q = i & 80, B = i & 1536, F = NaN, r = 0, o = 0; r < b.length; ++r) {
            var j = b[r].toLowerCase(),
              I = j.charCodeAt(0),
              R,
              v,
              P,
              S,
              D,
              w;
            if (32 === I) j = '';
            else if (34 === I)
              j =
                16 === q
                  ? k
                    ? g(x(M(e, X(b[r].substring(1, j.length - 1))), f, k))
                    : ''
                  : i & 8 && !(q & (q - 1))
                  ? j
                  : '';
            else if ('inherit' !== j) {
              if ((D = u.cssLitGroup)) {
                var G;
                if (!(G = u.cssLitMap)) {
                  G = {};
                  for (var K = D.length; 0 <= --K; ) for (var A = D[K], T = A.length; 0 <= --T; ) G[A[T]] = a;
                  G = u.cssLitMap = G;
                }
                D = G;
              } else D = a;
              if (!((w = D), w[E(j)] === a))
                if (35 === I && /^#(?:[0-9a-f]{3}){1,2}$/.test(j)) j = i & 2 ? j : '';
                else if (48 <= I && 57 >= I) j = i & 1 ? j : '';
                else if (
                  ((R = j.charCodeAt(1)),
                  (v = j.charCodeAt(2)),
                  (P = 48 <= R && 57 >= R),
                  (S = 48 <= v && 57 >= v),
                  43 === I && (P || (46 === R && S)))
                )
                  j = i & 1 ? (P ? '' : '0') + j.substring(1) : '';
                else if (45 === I && (P || (46 === R && S)))
                  j = i & 4 ? (P ? '-' : '-0') + j.substring(1) : i & 1 ? '0' : '';
                else if (46 === I && P) j = i & 1 ? '0' + j : '';
                else if ('url("' === j.substring(0, 5))
                  j = k && i & 16 ? g(x(M(e, b[r].substring(5, j.length - 2)), f, k)) : '';
                else if ('(' === j.charAt(j.length - 1))
                  a: {
                    D = b;
                    G = r;
                    j = 1;
                    K = G + 1;
                    for (I = D.length; K < I && j; ) (A = D[K++]), (j += ')' === A ? -1 : /^[^"']*\($/.test(A));
                    if (!j) {
                      j = D[G].toLowerCase();
                      I = E(j);
                      D = D.splice(G, K - G, '');
                      G = u.cssFns;
                      K = 0;
                      for (A = G.length; K < A; ++K)
                        if (G[K].substring(0, I.length) == I) {
                          D[0] = D[D.length - 1] = '';
                          y(G[K], D, k, e);
                          j = j + D.join(' ') + ')';
                          break a;
                        }
                    }
                    j = '';
                  }
                else
                  j =
                    B && /^-?[a-z_][\w\-]*$/.test(j) && !/__$/.test(j)
                      ? N && 512 === B
                        ? b[r] + N
                        : 1024 === B && L[j] && 'number' === typeof L[j].oa
                        ? j
                        : ''
                      : /^\w+$/.test(j) && 64 === q && i & 8
                      ? F + 1 === o
                        ? ((b[F] = b[F].substring(0, b[F].length - 1) + ' ' + j + '"'), '')
                        : ((F = o), '"' + j + '"')
                      : '';
            }
            j && (b[o++] = j);
          }
          1 === o && 'url("about:blank")' === b[0] && (o = 0);
          b.length = o;
        }
      };
    })();
    var k = RegExp(
        '^(active|after|before|blank|checked|default|disabled|drop|empty|enabled|first|first-child|first-letter|first-line|first-of-type|fullscreen|focus|hover|in-range|indeterminate|invalid|last-child|last-of-type|left|link|only-child|only-of-type|optional|out-of-range|placeholder-shown|read-only|read-write|required|right|root|scope|user-error|valid|visited)$'
      ),
      f = {};
    f['>'] = f['+'] = f['~'] = f;
    ba = function (a, d, b) {
      function g(i, r) {
        function o(b, f, g) {
          var y,
            e,
            i,
            l,
            o,
            m = n;
          y = '';
          if (b < f)
            if (((o = a[b]), '*' === o)) ++b, (y = o);
            else if (/^[a-zA-Z]/.test(o) && (e = x(o.toLowerCase(), [])))
              'tagName' in e && (o = e.tagName), ++b, (y = o);
          for (l = i = e = ''; m && b < f; ++b)
            if (((o = a[b]), '#' === o.charAt(0))) /^#_|__$|[^\w#:\-]/.test(o) ? (m = C) : (e += o + v);
            else if ('.' === o)
              ++b < f && /^[0-9A-Za-z:_\-]+$/.test((o = a[b])) && !/^_|__$/.test(o) ? (e += '.' + o) : (m = C);
            else if (b + 1 < f && '[' === a[b]) {
              ++b;
              var H = a[b++].toLowerCase();
              o = $.m[y + '::' + H];
              o !== +o && (o = $.m['*::' + H]);
              var W;
              d.ia
                ? ((W = d.ia(y, H)), 'string' !== typeof W && ((m = C), (W = H)), m && o !== +o && (o = $.d.NONE))
                : ((W = H), o !== +o && (m = C));
              var p = (H = ''),
                ca = C;
              /^[~^$*|]?=$/.test(a[b]) &&
                ((H = a[b++]),
                (p = a[b++]),
                /^[0-9A-Za-z:_\-]+$/.test(p) ? (p = '"' + p + '"') : ']' === p && ((p = '""'), --b),
                /^"([^\"\\]|\\.)*"$/.test(p) || (m = C),
                (ca = 'i' === a[b]) && ++b);
              ']' !== a[b] && (++b, (m = C));
              switch (o) {
                case $.d.CLASSES:
                case $.d.LOCAL_NAME:
                case $.d.NONE:
                  break;
                case $.d.GLOBAL_NAME:
                case $.d.ID:
                case $.d.IDREF:
                  ('=' === H || '~=' === H || '$=' === H) && '""' != p && !ca
                    ? (p = '"' + p.substring(1, p.length - 1) + v + '"')
                    : '|=' === H || '' === H || (m = C);
                  break;
                case $.d.URI:
                case $.d.URI_FRAGMENT:
                  '' !== H && (m = C);
                  break;
                default:
                  m = C;
              }
              m && (i += '[' + W.replace(/[^\w-]/g, '\\$&') + H + p + (ca ? ' i]' : ']'));
            } else if (b < f && ':' === a[b])
              if (((o = a[++b]), k.test(o))) l += ':' + o;
              else break;
            else break;
          b !== f && (m = C);
          m && (b = (y + e).replace(/[^ .*#\w-]/g, '\\$&') + i + l + g) && j.push(b);
          return m;
        }
        ' ' === a[i] && ++i;
        r - 1 !== i && ' ' === a[r] && --r;
        for (var j = [], l = i, q = n, u = i; q && u < r; ++u) {
          var B = a[u];
          if (f[B] === f || ' ' === B) o(l, u, B) ? (l = u + 1) : (q = C);
        }
        o(l, r, '') || (q = C);
        return q
          ? (j.length && ((l = j.join('')), e !== s && (l = '.' + e + ' ' + l), N.push(l)), n)
          : !b || b(a.slice(i, r));
      }
      var e = d.na,
        v = d.L,
        x = d.Aa,
        N = [],
        u = 0,
        i,
        q = 0,
        B;
      for (i = 0; i < a.length; ++i)
        if (
          ((B = a[i]),
          '(' == B || '[' == B
            ? (++q, n)
            : ')' == B || ']' == B
            ? (q && --q, n)
            : !(' ' == a[i] && (q || f[a[i - 1]] === f || f[a[i + 1]] === f)))
        )
          a[u++] = a[i];
      a.length = u;
      u = a.length;
      for (i = q = 0; i < u; ++i)
        if (',' === a[i]) {
          if (!g(q, i)) return s;
          q = i + 1;
        }
      return !g(q, u) ? s : N;
    };
    (function () {
      var a = /^\w/,
        d = RegExp(
          '^(?:(?:(?:(?:only|not) )?(?:all|aural|braille|embossed|handheld|print|projection|screen|speech|tty|tv)|\\( (?:(?:min-|max-)?(?:(?:device-)?(?:aspect-ratio|height|width)|color(?:-index)?|monochrome|orientation|resolution)|grid|hover|luminosity|pointer|scan|script) (?:: -?(?:[a-z]\\w+(?:-\\w+)*|\\d+(?: / \\d+|(?:\\.\\d+)?(?:p[cxt]|[cem]m|in|dpi|dppx|dpcm|%)?)) )?\\))(?: and ?\\( (?:(?:min-|max-)?(?:(?:device-)?(?:aspect-ratio|height|width)|color(?:-index)?|monochrome|orientation|resolution)|grid|hover|luminosity|pointer|scan|script) (?:: -?(?:[a-z]\\w+(?:-\\w+)*|\\d+(?: / \\d+|(?:\\.\\d+)?(?:p[cxt]|[cem]m|in|dpi|dppx|dpcm|%)?)) )?\\))*)(?: , (?:(?:(?:(?:only|not) )?(?:all|aural|braille|embossed|handheld|print|projection|screen|speech|tty|tv)|\\( (?:(?:min-|max-)?(?:(?:device-)?(?:aspect-ratio|height|width)|color(?:-index)?|monochrome|orientation|resolution)|grid|hover|luminosity|pointer|scan|script) (?:: -?(?:[a-z]\\w+(?:-\\w+)*|\\d+(?: / \\d+|(?:\\.\\d+)?(?:p[cxt]|[cem]m|in|dpi|dppx|dpcm|%)?)) )?\\))(?: and ?\\( (?:(?:min-|max-)?(?:(?:device-)?(?:aspect-ratio|height|width)|color(?:-index)?|monochrome|orientation|resolution)|grid|hover|luminosity|pointer|scan|script) (?:: -?(?:[a-z]\\w+(?:-\\w+)*|\\d+(?: / \\d+|(?:\\.\\d+)?(?:p[cxt]|[cem]m|in|dpi|dppx|dpcm|%)?)) )?\\))*))*$',
          'i'
        );
      Z = function (b) {
        for (var b = b.slice(), f = b.length, g = 0, k = 0; k < f; ++k) {
          var e = b[k];
          ' ' != e && (b[g++] = e);
        }
        b.length = g;
        b = b.join(' ');
        return (b = !b.length ? '' : !d.test(b) ? 'not all' : a.test(b) ? b : 'not all , ' + b);
      };
    })();
    (function () {
      function a(b) {
        var d = /^\s*[']([^']*)[']\s*$/,
          f = /^\s*url\s*[(]["]([^"]*)["][)]\s*$/,
          g = /^\s*url\s*[(][']([^']*)['][)]\s*$/,
          h = /^\s*url\s*[(]([^)]*)[)]\s*$/,
          k;
        return (k = /^\s*["]([^"]*)["]\s*$/.exec(b)) ||
          (k = d.exec(b)) ||
          (k = f.exec(b)) ||
          (k = g.exec(b)) ||
          (k = h.exec(b))
          ? k[1]
          : s;
      }
      function b(f, g, k, e, v, w, u) {
        function i() {
          r = F.length && F[F.length - 1] === s;
        }
        var q = c,
          B = u || [0],
          F = [],
          r = C;
        fa(g, {
          startStylesheet: function () {
            q = [];
          },
          endStylesheet: function () {},
          startAtrule: function (g, j) {
            if (r) g = s;
            else if ('@media' === g) q.push('@media', ' ', Z(j));
            else if ('@keyframes' === g || '@-webkit-keyframes' === g) {
              var i = j[0];
              1 === j.length && !/__$|[^\w\-]/.test(i) ? (q.push(g, ' ', i + k.L), (g = '@keyframes')) : (g = s);
            } else if ('@import' === g && 0 < j.length)
              if (((g = s), 'function' === typeof w)) {
                var l = Z(j.slice(1));
                if ('not all' !== l) {
                  ++B[0];
                  var u = [];
                  q.push(u);
                  var E = x(
                    M(f, a(j[0])),
                    function (a) {
                      var f = b(E, a.qa, k, e, v, w, B);
                      --B[0];
                      a = l
                        ? {
                            toString: function () {
                              return '@media ' + l + ' {' + f.result + '}';
                            },
                          }
                        : f.result;
                      u[0] = a;
                      w(a, !!B[0]);
                    },
                    v
                  );
                }
              } else window.console && window.console.log('@import ' + j.join(' ') + ' elided');
            r = !g;
            F.push(g);
          },
          endAtrule: function () {
            F.pop();
            r || q.push(';');
            i();
          },
          startBlock: function () {
            r || q.push('{');
          },
          endBlock: function () {
            r || (q.push('}'), (r = n));
          },
          startRuleset: function (a) {
            if (!r) {
              var b = c;
              '@keyframes' === F[F.length - 1]
                ? ((b = a.join(' ').match(/^ *(?:from|to|\d+(?:\.\d+)?%) *(?:, *(?:from|to|\d+(?:\.\d+)?%) *)*$/i)),
                  (r = !b),
                  b && (b = b[0].replace(/ +/g, '')))
                : ((a = ba(a, k)), !a || !a.length ? (r = n) : (b = a.join(', ')));
              r || q.push(b, '{');
            }
            F.push(s);
          },
          endRuleset: function () {
            F.pop();
            r || q.push('}');
            i();
          },
          declaration: function (a, b) {
            if (!r) {
              var d = C,
                g = b.length;
              2 <= g && '!' === b[g - 2] && 'important' === b[g - 1].toLowerCase() && ((d = n), (b.length -= 2));
              aa(a, b, e, f, k.L);
              b.length && q.push(a, ':', b.join(' '), d ? ' !important;' : ';');
            }
          },
        });
        return {
          result: {
            toString: function () {
              return q.join('');
            },
          },
          va: !!B[0],
        };
      }
      da = function (a, f, g, k) {
        return b(a, f, g, k, c, c).result.toString();
      };
    })();
  })();
  'undefined' !== typeof window &&
    ((window.sanitizeCssProperty = aa),
    (window.sanitizeCssSelectorList = ba),
    (window.sanitizeStylesheet = da),
    (window.sanitizeMediaQuery = Z));
  var fa, ga;
  (function () {
    function g(b, a, g, f, h) {
      for (var d = a++; a < g && '{' !== b[a] && ';' !== b[a]; ) ++a;
      if (a < g && (h || ';' === b[a])) {
        var h = d + 1,
          e = a;
        h < g && ' ' === b[h] && ++h;
        e > h && ' ' === b[e - 1] && --e;
        f.startAtrule && f.startAtrule(b[d].toLowerCase(), b.slice(h, e));
        a = '{' === b[a] ? w(b, a, g, f) : a + 1;
        f.endAtrule && f.endAtrule();
      }
      return a;
    }
    function w(b, a, k, f) {
      ++a;
      for (f.startBlock && f.startBlock(); a < k; ) {
        var h = b[a].charAt(0);
        if ('}' == h) {
          ++a;
          break;
        }
        a = ' ' === h || ';' === h ? a + 1 : '@' === h ? g(b, a, k, f, C) : '{' === h ? w(b, a, k, f) : M(b, a, k, f);
      }
      f.endBlock && f.endBlock();
      return a;
    }
    function M(b, a, g, f) {
      var h = a,
        d = x(b, a, g, n);
      if (0 > d) return (d = ~d), d === h ? d + 1 : d;
      var y = b[d];
      if ('{' !== y) return d === h ? d + 1 : d;
      a = d + 1;
      d > h && ' ' === b[d - 1] && --d;
      for (f.startRuleset && f.startRuleset(b.slice(h, d)); a < g; ) {
        y = b[a];
        if ('}' === y) {
          ++a;
          break;
        }
        a = ' ' === y ? a + 1 : e(b, a, g, f);
      }
      f.endRuleset && f.endRuleset();
      return a;
    }
    function x(b, a, g, f) {
      for (var h, d = [], e = -1; a < g; ++a)
        if (((h = b[a].charAt(0)), '[' === h || '(' === h)) d[++e] = h;
        else if ((']' === h && '[' === d[e]) || (')' === h && '(' === d[e])) --e;
        else if ('{' === h || '}' === h || ';' === h || '@' === h || (':' === h && !f)) break;
      0 <= e && (a = ~(a + 1));
      return a;
    }
    function E(b, a, g) {
      for (; a < g && ';' !== b[a] && '}' !== b[a]; ) ++a;
      return a < g && ';' === b[a] ? a + 1 : a;
    }
    function e(b, a, g, f) {
      var h = b[a++];
      if (!v.test(h)) return E(b, a, g);
      a < g && ' ' === b[a] && ++a;
      if (a == g || ':' !== b[a]) return E(b, a, g);
      ++a;
      a < g && ' ' === b[a] && ++a;
      var d = x(b, a, g, C);
      if (0 > d) d = ~d;
      else {
        for (var e = [], l = 0, w = a; w < d; ++w) (a = b[w]), ' ' !== a && (e[l++] = a);
        if (d < g) {
          do {
            a = b[d];
            if (';' === a || '}' === a) break;
            l = 0;
          } while (++d < g);
          ';' === a && ++d;
        }
        l && f.declaration && f.declaration(h.toLowerCase(), e);
      }
      return d;
    }
    fa = function (b, a) {
      var e = U(b);
      a.startStylesheet && a.startStylesheet();
      for (var f = 0, h = e.length; f < h; )
        f = ' ' === e[f] ? f + 1 : f < h ? ('@' === e[f].charAt(0) ? g(e, f, h, a, n) : M(e, f, h, a)) : f;
      a.endStylesheet && a.endStylesheet();
    };
    var v = /^-?[a-z]/i;
    ga = function (b, a) {
      for (var g = U(b), f = 0, h = g.length; f < h; ) f = ' ' !== g[f] ? e(g, f, h, a) : f + 1;
    };
  })();
  'undefined' !== typeof window && ((window.parseCssStylesheet = fa), (window.parseCssDeclarations = ga));
  var $ = {
    d: {
      NONE: 0,
      URI: 1,
      URI_FRAGMENT: 11,
      SCRIPT: 2,
      STYLE: 3,
      HTML: 12,
      ID: 4,
      IDREF: 5,
      IDREFS: 6,
      GLOBAL_NAME: 7,
      LOCAL_NAME: 8,
      CLASSES: 9,
      FRAME_TARGET: 10,
      MEDIA_QUERY: 13,
    },
  };
  $.atype = $.d;
  $.m = {
    '*::class': 9,
    '*::dir': 0,
    '*::draggable': 0,
    '*::hidden': 0,
    '*::id': 4,
    '*::inert': 0,
    '*::itemprop': 0,
    '*::itemref': 6,
    '*::itemscope': 0,
    '*::lang': 0,
    '*::onblur': 2,
    '*::onchange': 2,
    '*::onclick': 2,
    '*::ondblclick': 2,
    '*::onerror': 2,
    '*::onfocus': 2,
    '*::onkeydown': 2,
    '*::onkeypress': 2,
    '*::onkeyup': 2,
    '*::onload': 2,
    '*::onmousedown': 2,
    '*::onmousemove': 2,
    '*::onmouseout': 2,
    '*::onmouseover': 2,
    '*::onmouseup': 2,
    '*::onreset': 2,
    '*::onscroll': 2,
    '*::onselect': 2,
    '*::onsubmit': 2,
    '*::ontouchcancel': 2,
    '*::ontouchend': 2,
    '*::ontouchenter': 2,
    '*::ontouchleave': 2,
    '*::ontouchmove': 2,
    '*::ontouchstart': 2,
    '*::onunload': 2,
    '*::spellcheck': 0,
    '*::style': 3,
    '*::tabindex': 0,
    '*::title': 0,
    '*::translate': 0,
    'a::accesskey': 0,
    'a::coords': 0,
    'a::href': 1,
    'a::hreflang': 0,
    'a::name': 7,
    'a::onblur': 2,
    'a::onfocus': 2,
    'a::shape': 0,
    'a::target': 10,
    'a::type': 0,
    'area::accesskey': 0,
    'area::alt': 0,
    'area::coords': 0,
    'area::href': 1,
    'area::nohref': 0,
    'area::onblur': 2,
    'area::onfocus': 2,
    'area::shape': 0,
    'area::target': 10,
    'audio::controls': 0,
    'audio::loop': 0,
    'audio::mediagroup': 5,
    'audio::muted': 0,
    'audio::preload': 0,
    'audio::src': 1,
    'bdo::dir': 0,
    'blockquote::cite': 1,
    'br::clear': 0,
    'button::accesskey': 0,
    'button::disabled': 0,
    'button::name': 8,
    'button::onblur': 2,
    'button::onfocus': 2,
    'button::type': 0,
    'button::value': 0,
    'canvas::height': 0,
    'canvas::width': 0,
    'caption::align': 0,
    'col::align': 0,
    'col::char': 0,
    'col::charoff': 0,
    'col::span': 0,
    'col::valign': 0,
    'col::width': 0,
    'colgroup::align': 0,
    'colgroup::char': 0,
    'colgroup::charoff': 0,
    'colgroup::span': 0,
    'colgroup::valign': 0,
    'colgroup::width': 0,
    'command::checked': 0,
    'command::command': 5,
    'command::disabled': 0,
    'command::icon': 1,
    'command::label': 0,
    'command::radiogroup': 0,
    'command::type': 0,
    'data::value': 0,
    'del::cite': 1,
    'del::datetime': 0,
    'details::open': 0,
    'dir::compact': 0,
    'div::align': 0,
    'dl::compact': 0,
    'fieldset::disabled': 0,
    'font::color': 0,
    'font::face': 0,
    'font::size': 0,
    'form::accept': 0,
    'form::action': 1,
    'form::autocomplete': 0,
    'form::enctype': 0,
    'form::method': 0,
    'form::name': 7,
    'form::novalidate': 0,
    'form::onreset': 2,
    'form::onsubmit': 2,
    'form::target': 10,
    'h1::align': 0,
    'h2::align': 0,
    'h3::align': 0,
    'h4::align': 0,
    'h5::align': 0,
    'h6::align': 0,
    'hr::align': 0,
    'hr::noshade': 0,
    'hr::size': 0,
    'hr::width': 0,
    'iframe::align': 0,
    'iframe::frameborder': 0,
    'iframe::height': 0,
    'iframe::marginheight': 0,
    'iframe::marginwidth': 0,
    'iframe::width': 0,
    'img::align': 0,
    'img::alt': 0,
    'img::border': 0,
    'img::height': 0,
    'img::hspace': 0,
    'img::ismap': 0,
    'img::name': 7,
    'img::src': 1,
    'img::usemap': 11,
    'img::vspace': 0,
    'img::width': 0,
    'input::accept': 0,
    'input::accesskey': 0,
    'input::align': 0,
    'input::alt': 0,
    'input::autocomplete': 0,
    'input::checked': 0,
    'input::disabled': 0,
    'input::inputmode': 0,
    'input::ismap': 0,
    'input::list': 5,
    'input::max': 0,
    'input::maxlength': 0,
    'input::min': 0,
    'input::multiple': 0,
    'input::name': 8,
    'input::onblur': 2,
    'input::onchange': 2,
    'input::onfocus': 2,
    'input::onselect': 2,
    'input::pattern': 0,
    'input::placeholder': 0,
    'input::readonly': 0,
    'input::required': 0,
    'input::size': 0,
    'input::src': 1,
    'input::step': 0,
    'input::type': 0,
    'input::usemap': 11,
    'input::value': 0,
    'ins::cite': 1,
    'ins::datetime': 0,
    'label::accesskey': 0,
    'label::for': 5,
    'label::onblur': 2,
    'label::onfocus': 2,
    'legend::accesskey': 0,
    'legend::align': 0,
    'li::type': 0,
    'li::value': 0,
    'map::name': 7,
    'menu::compact': 0,
    'menu::label': 0,
    'menu::type': 0,
    'meter::high': 0,
    'meter::low': 0,
    'meter::max': 0,
    'meter::min': 0,
    'meter::value': 0,
    'ol::compact': 0,
    'ol::reversed': 0,
    'ol::start': 0,
    'ol::type': 0,
    'optgroup::disabled': 0,
    'optgroup::label': 0,
    'option::disabled': 0,
    'option::label': 0,
    'option::selected': 0,
    'option::value': 0,
    'output::for': 6,
    'output::name': 8,
    'p::align': 0,
    'pre::width': 0,
    'progress::max': 0,
    'progress::min': 0,
    'progress::value': 0,
    'q::cite': 1,
    'select::autocomplete': 0,
    'select::disabled': 0,
    'select::multiple': 0,
    'select::name': 8,
    'select::onblur': 2,
    'select::onchange': 2,
    'select::onfocus': 2,
    'select::required': 0,
    'select::size': 0,
    'source::type': 0,
    'table::align': 0,
    'table::bgcolor': 0,
    'table::border': 0,
    'table::cellpadding': 0,
    'table::cellspacing': 0,
    'table::frame': 0,
    'table::rules': 0,
    'table::summary': 0,
    'table::width': 0,
    'tbody::align': 0,
    'tbody::char': 0,
    'tbody::charoff': 0,
    'tbody::valign': 0,
    'td::abbr': 0,
    'td::align': 0,
    'td::axis': 0,
    'td::bgcolor': 0,
    'td::char': 0,
    'td::charoff': 0,
    'td::colspan': 0,
    'td::headers': 6,
    'td::height': 0,
    'td::nowrap': 0,
    'td::rowspan': 0,
    'td::scope': 0,
    'td::valign': 0,
    'td::width': 0,
    'textarea::accesskey': 0,
    'textarea::autocomplete': 0,
    'textarea::cols': 0,
    'textarea::disabled': 0,
    'textarea::inputmode': 0,
    'textarea::name': 8,
    'textarea::onblur': 2,
    'textarea::onchange': 2,
    'textarea::onfocus': 2,
    'textarea::onselect': 2,
    'textarea::placeholder': 0,
    'textarea::readonly': 0,
    'textarea::required': 0,
    'textarea::rows': 0,
    'textarea::wrap': 0,
    'tfoot::align': 0,
    'tfoot::char': 0,
    'tfoot::charoff': 0,
    'tfoot::valign': 0,
    'th::abbr': 0,
    'th::align': 0,
    'th::axis': 0,
    'th::bgcolor': 0,
    'th::char': 0,
    'th::charoff': 0,
    'th::colspan': 0,
    'th::headers': 6,
    'th::height': 0,
    'th::nowrap': 0,
    'th::rowspan': 0,
    'th::scope': 0,
    'th::valign': 0,
    'th::width': 0,
    'thead::align': 0,
    'thead::char': 0,
    'thead::charoff': 0,
    'thead::valign': 0,
    'tr::align': 0,
    'tr::bgcolor': 0,
    'tr::char': 0,
    'tr::charoff': 0,
    'tr::valign': 0,
    'track::default': 0,
    'track::kind': 0,
    'track::label': 0,
    'track::srclang': 0,
    'ul::compact': 0,
    'ul::type': 0,
    'video::controls': 0,
    'video::height': 0,
    'video::loop': 0,
    'video::mediagroup': 5,
    'video::muted': 0,
    'video::poster': 1,
    'video::preload': 0,
    'video::src': 1,
    'video::width': 0,
  };
  $.ATTRIBS = $.m;
  $.c = {
    OPTIONAL_ENDTAG: 1,
    EMPTY: 2,
    CDATA: 4,
    RCDATA: 8,
    UNSAFE: 16,
    FOLDABLE: 32,
    SCRIPT: 64,
    STYLE: 128,
    VIRTUALIZED: 256,
  };
  $.eflags = $.c;
  $.f = {
    a: 0,
    abbr: 0,
    acronym: 0,
    address: 0,
    applet: 272,
    area: 2,
    article: 0,
    aside: 0,
    audio: 0,
    b: 0,
    base: 274,
    basefont: 274,
    bdi: 0,
    bdo: 0,
    big: 0,
    blockquote: 0,
    body: 305,
    br: 2,
    button: 0,
    canvas: 0,
    caption: 0,
    center: 0,
    cite: 0,
    code: 0,
    col: 2,
    colgroup: 1,
    command: 2,
    data: 0,
    datalist: 0,
    dd: 1,
    del: 0,
    details: 0,
    dfn: 0,
    dialog: 272,
    dir: 0,
    div: 0,
    dl: 0,
    dt: 1,
    em: 0,
    fieldset: 0,
    figcaption: 0,
    figure: 0,
    font: 0,
    footer: 0,
    form: 0,
    frame: 274,
    frameset: 272,
    h1: 0,
    h2: 0,
    h3: 0,
    h4: 0,
    h5: 0,
    h6: 0,
    head: 305,
    header: 0,
    hgroup: 0,
    hr: 2,
    html: 305,
    i: 0,
    iframe: 4,
    img: 2,
    input: 2,
    ins: 0,
    isindex: 274,
    kbd: 0,
    keygen: 274,
    label: 0,
    legend: 0,
    li: 1,
    link: 274,
    map: 0,
    mark: 0,
    menu: 0,
    meta: 274,
    meter: 0,
    nav: 0,
    nobr: 0,
    noembed: 276,
    noframes: 276,
    noscript: 276,
    object: 272,
    ol: 0,
    optgroup: 0,
    option: 1,
    output: 0,
    p: 1,
    param: 274,
    pre: 0,
    progress: 0,
    q: 0,
    s: 0,
    samp: 0,
    script: 84,
    section: 0,
    select: 0,
    small: 0,
    source: 2,
    span: 0,
    strike: 0,
    strong: 0,
    style: 148,
    sub: 0,
    summary: 0,
    sup: 0,
    table: 0,
    tbody: 1,
    td: 1,
    textarea: 8,
    tfoot: 1,
    th: 1,
    thead: 1,
    time: 0,
    title: 280,
    tr: 1,
    track: 2,
    tt: 0,
    u: 0,
    ul: 0,
    var: 0,
    video: 0,
    wbr: 2,
  };
  $.ELEMENTS = $.f;
  $.Q = {
    a: 'HTMLAnchorElement',
    abbr: 'HTMLElement',
    acronym: 'HTMLElement',
    address: 'HTMLElement',
    applet: 'HTMLAppletElement',
    area: 'HTMLAreaElement',
    article: 'HTMLElement',
    aside: 'HTMLElement',
    audio: 'HTMLAudioElement',
    b: 'HTMLElement',
    base: 'HTMLBaseElement',
    basefont: 'HTMLBaseFontElement',
    bdi: 'HTMLElement',
    bdo: 'HTMLElement',
    big: 'HTMLElement',
    blockquote: 'HTMLQuoteElement',
    body: 'HTMLBodyElement',
    br: 'HTMLBRElement',
    button: 'HTMLButtonElement',
    canvas: 'HTMLCanvasElement',
    caption: 'HTMLTableCaptionElement',
    center: 'HTMLElement',
    cite: 'HTMLElement',
    code: 'HTMLElement',
    col: 'HTMLTableColElement',
    colgroup: 'HTMLTableColElement',
    command: 'HTMLCommandElement',
    data: 'HTMLElement',
    datalist: 'HTMLDataListElement',
    dd: 'HTMLElement',
    del: 'HTMLModElement',
    details: 'HTMLDetailsElement',
    dfn: 'HTMLElement',
    dialog: 'HTMLDialogElement',
    dir: 'HTMLDirectoryElement',
    div: 'HTMLDivElement',
    dl: 'HTMLDListElement',
    dt: 'HTMLElement',
    em: 'HTMLElement',
    fieldset: 'HTMLFieldSetElement',
    figcaption: 'HTMLElement',
    figure: 'HTMLElement',
    font: 'HTMLFontElement',
    footer: 'HTMLElement',
    form: 'HTMLFormElement',
    frame: 'HTMLFrameElement',
    frameset: 'HTMLFrameSetElement',
    h1: 'HTMLHeadingElement',
    h2: 'HTMLHeadingElement',
    h3: 'HTMLHeadingElement',
    h4: 'HTMLHeadingElement',
    h5: 'HTMLHeadingElement',
    h6: 'HTMLHeadingElement',
    head: 'HTMLHeadElement',
    header: 'HTMLElement',
    hgroup: 'HTMLElement',
    hr: 'HTMLHRElement',
    html: 'HTMLHtmlElement',
    i: 'HTMLElement',
    iframe: 'HTMLIFrameElement',
    img: 'HTMLImageElement',
    input: 'HTMLInputElement',
    ins: 'HTMLModElement',
    isindex: 'HTMLUnknownElement',
    kbd: 'HTMLElement',
    keygen: 'HTMLKeygenElement',
    label: 'HTMLLabelElement',
    legend: 'HTMLLegendElement',
    li: 'HTMLLIElement',
    link: 'HTMLLinkElement',
    map: 'HTMLMapElement',
    mark: 'HTMLElement',
    menu: 'HTMLMenuElement',
    meta: 'HTMLMetaElement',
    meter: 'HTMLMeterElement',
    nav: 'HTMLElement',
    nobr: 'HTMLElement',
    noembed: 'HTMLElement',
    noframes: 'HTMLElement',
    noscript: 'HTMLElement',
    object: 'HTMLObjectElement',
    ol: 'HTMLOListElement',
    optgroup: 'HTMLOptGroupElement',
    option: 'HTMLOptionElement',
    output: 'HTMLOutputElement',
    p: 'HTMLParagraphElement',
    param: 'HTMLParamElement',
    pre: 'HTMLPreElement',
    progress: 'HTMLProgressElement',
    q: 'HTMLQuoteElement',
    s: 'HTMLElement',
    samp: 'HTMLElement',
    script: 'HTMLScriptElement',
    section: 'HTMLElement',
    select: 'HTMLSelectElement',
    small: 'HTMLElement',
    source: 'HTMLSourceElement',
    span: 'HTMLSpanElement',
    strike: 'HTMLElement',
    strong: 'HTMLElement',
    style: 'HTMLStyleElement',
    sub: 'HTMLElement',
    summary: 'HTMLElement',
    sup: 'HTMLElement',
    table: 'HTMLTableElement',
    tbody: 'HTMLTableSectionElement',
    td: 'HTMLTableDataCellElement',
    textarea: 'HTMLTextAreaElement',
    tfoot: 'HTMLTableSectionElement',
    th: 'HTMLTableHeaderCellElement',
    thead: 'HTMLTableSectionElement',
    time: 'HTMLTimeElement',
    title: 'HTMLTitleElement',
    tr: 'HTMLTableRowElement',
    track: 'HTMLTrackElement',
    tt: 'HTMLElement',
    u: 'HTMLElement',
    ul: 'HTMLUListElement',
    var: 'HTMLElement',
    video: 'HTMLVideoElement',
    wbr: 'HTMLElement',
  };
  $.ELEMENT_DOM_INTERFACES = $.Q;
  $.P = { NOT_LOADED: 0, SAME_DOCUMENT: 1, NEW_DOCUMENT: 2 };
  $.ueffects = $.P;
  $.J = {
    'a::href': 2,
    'area::href': 2,
    'audio::src': 1,
    'blockquote::cite': 0,
    'command::icon': 1,
    'del::cite': 0,
    'form::action': 2,
    'img::src': 1,
    'input::src': 1,
    'ins::cite': 0,
    'q::cite': 0,
    'video::poster': 1,
    'video::src': 1,
  };
  $.URIEFFECTS = $.J;
  $.M = { UNSANDBOXED: 2, SANDBOXED: 1, DATA: 0 };
  $.ltypes = $.M;
  $.I = {
    'a::href': 2,
    'area::href': 2,
    'audio::src': 2,
    'blockquote::cite': 2,
    'command::icon': 1,
    'del::cite': 2,
    'form::action': 2,
    'img::src': 1,
    'input::src': 1,
    'ins::cite': 2,
    'q::cite': 2,
    'video::poster': 1,
    'video::src': 2,
  };
  $.LOADERTYPES = $.I;
  'undefined' !== typeof window && (window.html4 = $);
  var ha = (function (g) {
      function w(a) {
        if (i.hasOwnProperty(a)) return i[a];
        var b = a.match(q);
        return b
          ? String.fromCharCode(parseInt(b[1], 10))
          : (b = a.match(B))
          ? String.fromCharCode(parseInt(b[1], 16))
          : r && F.test(a)
          ? ((r.innerHTML = '&' + a + ';'), (b = r.textContent), (i[a] = b))
          : '&' + a + ';';
      }
      function M(a, b) {
        return w(b);
      }
      function x(a) {
        return a.replace(j, M);
      }
      function E(a) {
        return ('' + a).replace(R, '&amp;').replace(P, '&lt;').replace(S, '&gt;').replace(D, '&#34;');
      }
      function e(a) {
        return a.replace(ia, '&amp;$1').replace(P, '&lt;').replace(S, '&gt;');
      }
      function v(b) {
        var d = {
          z: b.z || b.cdata,
          A: b.A || b.comment,
          B: b.B || b.endDoc,
          t: b.t || b.endTag,
          e: b.e || b.pcdata,
          F: b.F || b.rcdata,
          H: b.H || b.startDoc,
          w: b.w || b.startTag,
        };
        return function (b, g) {
          var f;
          var H = /(<\/|<\!--|<[!?]|[&<>])/g;
          f = b + '';
          if (G) f = f.split(H);
          else {
            for (var e = [], h = 0, j; (j = H.exec(f)) !== s; )
              e.push(f.substring(h, j.index)), e.push(j[0]), (h = j.index + j[0].length);
            e.push(f.substring(h));
            f = e;
          }
          a(d, f, 0, { r: C, C: C }, g);
        };
      }
      function b(b, d, g, f, t) {
        return function () {
          a(b, d, g, f, t);
        };
      }
      function a(a, d, p, e, t) {
        try {
          a.H && 0 == p && a.H(t);
          for (var h, z, j, i = d.length; p < i; ) {
            var o = d[p++],
              l = d[p];
            switch (o) {
              case '&':
                I.test(l)
                  ? (a.e && a.e('&' + l, t, A, b(a, d, p, e, t)), p++)
                  : a.e && a.e('&amp;', t, A, b(a, d, p, e, t));
                break;
              case '</':
                if ((h = /^([-\w:]+)[^\'\"]*/.exec(l)))
                  if (h[0].length === l.length && '>' === d[p + 1])
                    (p += 2), (j = h[1].toLowerCase()), a.t && a.t(j, t, A, b(a, d, p, e, t));
                  else {
                    var m = d,
                      q = p,
                      r = a,
                      u = t,
                      v = A,
                      y = e,
                      w = f(m, q);
                    w ? (r.t && r.t(w.name, u, v, b(r, m, q, y, u)), (p = w.next)) : (p = m.length);
                  }
                else a.e && a.e('&lt;/', t, A, b(a, d, p, e, t));
                break;
              case '<':
                if ((h = /^([-\w:]+)\s*\/?/.exec(l)))
                  if (h[0].length === l.length && '>' === d[p + 1]) {
                    p += 2;
                    j = h[1].toLowerCase();
                    a.w && a.w(j, [], t, A, b(a, d, p, e, t));
                    var B = g.f[j];
                    B & K && (p = k(d, { name: j, next: p, c: B }, a, t, A, e));
                  } else {
                    var m = d,
                      q = a,
                      r = t,
                      u = A,
                      v = e,
                      x = f(m, p);
                    x
                      ? (q.w && q.w(x.name, x.R, r, u, b(q, m, x.next, v, r)),
                        (p = x.c & K ? k(m, x, q, r, u, v) : x.next))
                      : (p = m.length);
                  }
                else a.e && a.e('&lt;', t, A, b(a, d, p, e, t));
                break;
              case '<!--':
                if (!e.C) {
                  for (z = p + 1; z < i && !('>' === d[z] && /--$/.test(d[z - 1])); z++);
                  if (z < i) {
                    if (a.A) {
                      var D = d.slice(p, z).join('');
                      a.A(D.substr(0, D.length - 2), t, A, b(a, d, z + 1, e, t));
                    }
                    p = z + 1;
                  } else e.C = n;
                }
                e.C && a.e && a.e('&lt;!--', t, A, b(a, d, p, e, t));
                break;
              case '<!':
                if (/^\w/.test(l)) {
                  if (!e.r) {
                    for (z = p + 1; z < i && '>' !== d[z]; z++);
                    z < i ? (p = z + 1) : (e.r = n);
                  }
                  e.r && a.e && a.e('&lt;!', t, A, b(a, d, p, e, t));
                } else a.e && a.e('&lt;!', t, A, b(a, d, p, e, t));
                break;
              case '<?':
                if (!e.r) {
                  for (z = p + 1; z < i && '>' !== d[z]; z++);
                  z < i ? (p = z + 1) : (e.r = n);
                }
                e.r && a.e && a.e('&lt;?', t, A, b(a, d, p, e, t));
                break;
              case '>':
                a.e && a.e('&gt;', t, A, b(a, d, p, e, t));
                break;
              case '':
                break;
              default:
                a.e && a.e(o, t, A, b(a, d, p, e, t));
            }
          }
          a.B && a.B(t);
        } catch (E) {
          if (E !== A) throw E;
        }
      }
      function k(a, d, f, h, t, j) {
        var z = a.length;
        T.hasOwnProperty(d.name) || (T[d.name] = RegExp('^' + d.name + '(?:[\\s\\/]|$)', 'i'));
        for (var i = T[d.name], k = d.next, l = d.next + 1; l < z && !('</' === a[l - 1] && i.test(a[l])); l++);
        l < z && (l -= 1);
        z = a.slice(k, l).join('');
        if (d.c & g.c.CDATA) f.z && f.z(z, h, t, b(f, a, l, j, h));
        else if (d.c & g.c.RCDATA) f.F && f.F(e(z), h, t, b(f, a, l, j, h));
        else throw Error('bug');
        return l;
      }
      function f(a, b) {
        var d = /^([-\w:]+)/.exec(a[b]),
          f = {};
        f.name = d[1].toLowerCase();
        f.c = g.f[f.name];
        for (var e = a[b].substr(d[0].length), h = b + 1, j = a.length; h < j && '>' !== a[h]; h++) e += a[h];
        if (!(j <= h)) {
          for (var l = []; '' !== e; )
            if ((d = ja.exec(e)))
              if ((d[4] && !d[5]) || (d[6] && !d[7])) {
                for (var d = d[4] || d[6], i = C, e = [e, a[h++]]; h < j; h++) {
                  if (i) {
                    if ('>' === a[h]) break;
                  } else 0 <= a[h].indexOf(d) && (i = n);
                  e.push(a[h]);
                }
                if (j <= h) break;
                e = e.join('');
              } else {
                var i = d[1].toLowerCase(),
                  k;
                if (d[2]) {
                  k = d[3];
                  var m = k.charCodeAt(0);
                  if (34 === m || 39 === m) k = k.substr(1, k.length - 2);
                  k = x(k.replace(o, ''));
                } else k = '';
                l.push(i, k);
                e = e.substr(d[0].length);
              }
            else e = e.replace(/^[\s\S][^a-z\s]*/, '');
          f.R = l;
          f.next = h + 1;
          return f;
        }
      }
      function h(a) {
        function b(a, d) {
          f || d.push(a);
        }
        var d, f;
        return v({
          startDoc: function () {
            d = [];
            f = C;
          },
          startTag: function (b, e, h) {
            if (!f && g.f.hasOwnProperty(b)) {
              var j = g.f[b];
              if (!(j & g.c.FOLDABLE)) {
                var k = a(b, e);
                if (k) {
                  if ('object' !== typeof k) throw Error('tagPolicy did not return object (old API?)');
                  if ('attribs' in k) e = k.attribs;
                  else throw Error('tagPolicy gave no attribs');
                  var i;
                  'tagName' in k ? ((i = k.tagName), (k = g.f[i])) : ((i = b), (k = j));
                  if (j & g.c.OPTIONAL_ENDTAG) {
                    var l = d[d.length - 1];
                    l && l.D === b && (l.v !== i || b !== i) && h.push('</', l.v, '>');
                  }
                  j & g.c.EMPTY || d.push({ D: b, v: i });
                  h.push('<', i);
                  b = 0;
                  for (l = e.length; b < l; b += 2) {
                    var m = e[b],
                      o = e[b + 1];
                    o !== s && o !== c && h.push(' ', m, '="', E(o), '"');
                  }
                  h.push('>');
                  j & g.c.EMPTY && !(k & g.c.EMPTY) && h.push('</', i, '>');
                } else f = !(j & g.c.EMPTY);
              }
            }
          },
          endTag: function (a, b) {
            if (f) f = C;
            else if (g.f.hasOwnProperty(a)) {
              var e = g.f[a];
              if (!(e & (g.c.EMPTY | g.c.FOLDABLE))) {
                if (e & g.c.OPTIONAL_ENDTAG)
                  for (e = d.length; 0 <= --e; ) {
                    var h = d[e].D;
                    if (h === a) break;
                    if (!(g.f[h] & g.c.OPTIONAL_ENDTAG)) return;
                  }
                else for (e = d.length; 0 <= --e && d[e].D !== a; );
                if (!(0 > e)) {
                  for (h = d.length; --h > e; ) {
                    var j = d[h].v;
                    g.f[j] & g.c.OPTIONAL_ENDTAG || b.push('</', j, '>');
                  }
                  e < d.length && (a = d[e].v);
                  d.length = e;
                  b.push('</', a, '>');
                }
              }
            }
          },
          pcdata: b,
          rcdata: b,
          cdata: b,
          endDoc: function (a) {
            for (; d.length; d.length--) a.push('</', d[d.length - 1].v, '>');
          },
        });
      }
      function d(a, b, d, f, e) {
        if (!e) return s;
        try {
          var g = Y.parse('' + a);
          if (g && (!g.K() || ka.test(g.W()))) {
            var h = e(g, b, d, f);
            return h ? h.toString() : s;
          }
        } catch (j) {}
        return s;
      }
      function y(a, b, d, f, e) {
        d || a(b + ' removed', { S: 'removed', tagName: b });
        if (f !== e) {
          var g = 'changed';
          f && !e ? (g = 'removed') : !f && e && (g = 'added');
          a(b + '.' + d + ' ' + g, { S: g, tagName: b, la: d, oldValue: f, newValue: e });
        }
      }
      function l(a, b, d) {
        b = b + '::' + d;
        if (a.hasOwnProperty(b)) return a[b];
        b = '*::' + d;
        if (a.hasOwnProperty(b)) return a[b];
      }
      function V(a, b, f, e, h) {
        for (var j = 0; j < b.length; j += 2) {
          var k = b[j],
            i = b[j + 1],
            m = i,
            o = s,
            q;
          if (((q = a + '::' + k), g.m.hasOwnProperty(q)) || ((q = '*::' + k), g.m.hasOwnProperty(q))) o = g.m[q];
          if (o !== s)
            switch (o) {
              case g.d.NONE:
                break;
              case g.d.SCRIPT:
                i = s;
                h && y(h, a, k, m, i);
                break;
              case g.d.STYLE:
                if ('undefined' === typeof N) {
                  i = s;
                  h && y(h, a, k, m, i);
                  break;
                }
                var r = [];
                N(i, {
                  declaration: function (a, b) {
                    var e = a.toLowerCase();
                    u(
                      e,
                      b,
                      f
                        ? function (a) {
                            return d(a, g.P.ja, g.M.ka, { TYPE: 'CSS', CSS_PROP: e }, f);
                          }
                        : s
                    );
                    b.length && r.push(e + ': ' + b.join(' '));
                  },
                });
                i = 0 < r.length ? r.join(' ; ') : s;
                h && y(h, a, k, m, i);
                break;
              case g.d.ID:
              case g.d.IDREF:
              case g.d.IDREFS:
              case g.d.GLOBAL_NAME:
              case g.d.LOCAL_NAME:
              case g.d.CLASSES:
                i = e ? e(i) : i;
                h && y(h, a, k, m, i);
                break;
              case g.d.URI:
                i = d(i, l(g.J, a, k), l(g.I, a, k), { TYPE: 'MARKUP', XML_ATTR: k, XML_TAG: a }, f);
                h && y(h, a, k, m, i);
                break;
              case g.d.URI_FRAGMENT:
                i && '#' === i.charAt(0)
                  ? ((i = i.substring(1)), (i = e ? e(i) : i), i !== s && i !== c && (i = '#' + i))
                  : (i = s);
                h && y(h, a, k, m, i);
                break;
              default:
                (i = s), h && y(h, a, k, m, i);
            }
          else (i = s), h && y(h, a, k, m, i);
          b[j + 1] = i;
        }
        return b;
      }
      function ea(a, b, d) {
        return function (e, f) {
          if (g.f[e] & g.c.UNSAFE) d && y(d, e, c, c, c);
          else return { attribs: V(e, f, a, b, d) };
        };
      }
      function Q(a, b) {
        var d = [];
        h(b)(a, d);
        return d.join('');
      }
      var N, u;
      'undefined' !== typeof window && ((N = window.parseCssDeclarations), (u = window.sanitizeCssProperty));
      var i = { lt: '<', LT: '<', gt: '>', GT: '>', amp: '&', AMP: '&', quot: '"', apos: "'", nbsp: '\u00a0' },
        q = /^#(\d+)$/,
        B = /^#x([0-9A-Fa-f]+)$/,
        F = /^[A-Za-z][A-za-z0-9]+$/,
        r = 'undefined' !== typeof window && window.document ? window.document.createElement('textarea') : s,
        o = /\0/g,
        j = /&(#[0-9]+|#[xX][0-9A-Fa-f]+|\w+);/g,
        I = /^(#[0-9]+|#[xX][0-9A-Fa-f]+|\w+);/,
        R = /&/g,
        ia = /&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi,
        P = /[<]/g,
        S = />/g,
        D = /\"/g,
        ja = /^\s*([-.:\w]+)(?:\s*(=)\s*((")[^"]*("|$)|(')[^']*('|$)|(?=[a-z][-\w]*\s*=)|[^"'\s]*))?/i,
        G = 3 === 'a,b'.split(/(,)/).length,
        K = g.c.CDATA | g.c.RCDATA,
        A = {},
        T = {},
        ka = /^(?:https?|mailto|data)$/i,
        m = {};
      m.pa = m.escapeAttrib = E;
      m.ra = m.makeHtmlSanitizer = h;
      m.sa = m.makeSaxParser = v;
      m.ta = m.makeTagPolicy = ea;
      m.wa = m.normalizeRCData = e;
      m.xa = m.sanitize = function (a, b, d, e) {
        return Q(a, ea(b, d, e));
      };
      m.ya = m.sanitizeAttribs = V;
      m.za = m.sanitizeWithPolicy = Q;
      m.Ba = m.unescapeEntities = x;
      return m;
    })($),
    la = ha.sanitize;
  'undefined' !== typeof window && ((window.html = ha), (window.html_sanitize = la));
})();
/**
 * Copyright (c) 2006-2017, JGraph Ltd
 * Copyright (c) 2006-2017, Gaudenz Alder
 */

var mxClient = {
  /**
   * Class: mxClient
   *
   * Bootstrapping mechanism for the mxGraph thin client. The production version
   * of this file contains all code required to run the mxGraph thin client, as
   * well as global constants to identify the browser and operating system in
   * use. You may have to load chrome://global/content/contentAreaUtils.js in
   * your page to disable certain security restrictions in Mozilla.
   *
   * Variable: VERSION
   *
   * Contains the current version of the mxGraph library. The strings that
   * communicate versions of mxGraph use the following format.
   *
   * versionMajor.versionMinor.buildNumber.revisionNumber
   *
   * Current version is 4.2.2.
   */
  VERSION: '4.2.2',

  /**
   * Variable: IS_IE
   *
   * True if the current browser is Internet Explorer 10 or below. Use <mxClient.IS_IE11>
   * to detect IE 11.
   */
  IS_IE: navigator.userAgent != null && navigator.userAgent.indexOf('MSIE') >= 0,

  /**
   * Variable: IS_IE6
   *
   * True if the current browser is Internet Explorer 6.x.
   */
  IS_IE6: navigator.userAgent != null && navigator.userAgent.indexOf('MSIE 6') >= 0,

  /**
   * Variable: IS_IE11
   *
   * True if the current browser is Internet Explorer 11.x.
   */
  IS_IE11: navigator.userAgent != null && !!navigator.userAgent.match(/Trident\/7\./),

  /**
   * Variable: IS_EDGE
   *
   * True if the current browser is Microsoft Edge.
   */
  IS_EDGE: navigator.userAgent != null && !!navigator.userAgent.match(/Edge\//),

  /**
   * Variable: IS_QUIRKS
   *
   * True if the current browser is Internet Explorer and it is in quirks mode.
   */
  IS_QUIRKS:
    navigator.userAgent != null &&
    navigator.userAgent.indexOf('MSIE') >= 0 &&
    (document.documentMode == null || document.documentMode == 5),

  /**
   * Variable: IS_EM
   *
   * True if the browser is IE11 in enterprise mode (IE8 standards mode).
   */
  IS_EM: 'spellcheck' in document.createElement('textarea') && document.documentMode == 8,

  /**
   * Variable: VML_PREFIX
   *
   * Prefix for VML namespace in node names. Default is 'v'.
   */
  VML_PREFIX: 'v',

  /**
   * Variable: OFFICE_PREFIX
   *
   * Prefix for VML office namespace in node names. Default is 'o'.
   */
  OFFICE_PREFIX: 'o',

  /**
   * Variable: IS_NS
   *
   * True if the current browser is Netscape (including Firefox).
   */
  IS_NS:
    navigator.userAgent != null &&
    navigator.userAgent.indexOf('Mozilla/') >= 0 &&
    navigator.userAgent.indexOf('MSIE') < 0 &&
    navigator.userAgent.indexOf('Edge/') < 0,

  /**
   * Variable: IS_OP
   *
   * True if the current browser is Opera.
   */
  IS_OP:
    navigator.userAgent != null &&
    (navigator.userAgent.indexOf('Opera/') >= 0 || navigator.userAgent.indexOf('OPR/') >= 0),

  /**
   * Variable: IS_OT
   *
   * True if -o-transform is available as a CSS style, ie for Opera browsers
   * based on a Presto engine with version 2.5 or later.
   */
  IS_OT:
    navigator.userAgent != null &&
    navigator.userAgent.indexOf('Presto/') >= 0 &&
    navigator.userAgent.indexOf('Presto/2.4.') < 0 &&
    navigator.userAgent.indexOf('Presto/2.3.') < 0 &&
    navigator.userAgent.indexOf('Presto/2.2.') < 0 &&
    navigator.userAgent.indexOf('Presto/2.1.') < 0 &&
    navigator.userAgent.indexOf('Presto/2.0.') < 0 &&
    navigator.userAgent.indexOf('Presto/1.') < 0,

  /**
   * Variable: IS_SF
   *
   * True if the current browser is Safari.
   */
  IS_SF: /Apple Computer, Inc/.test(navigator.vendor),

  /**
   * Variable: IS_ANDROID
   *
   * Returns true if the user agent contains Android.
   */
  IS_ANDROID: navigator.appVersion.indexOf('Android') >= 0,

  /**
   * Variable: IS_IOS
   *
   * Returns true if the user agent is an iPad, iPhone or iPod.
   */
  IS_IOS: /iP(hone|od|ad)/.test(navigator.platform),

  /**
   * Variable: IS_GC
   *
   * True if the current browser is Google Chrome.
   */
  IS_GC: /Google Inc/.test(navigator.vendor),

  /**
   * Variable: IS_CHROMEAPP
   *
   * True if the this is running inside a Chrome App.
   */
  IS_CHROMEAPP: window.chrome != null && chrome.app != null && chrome.app.runtime != null,

  /**
   * Variable: IS_FF
   *
   * True if the current browser is Firefox.
   */
  IS_FF: typeof InstallTrigger !== 'undefined',

  /**
   * Variable: IS_MT
   *
   * True if -moz-transform is available as a CSS style. This is the case
   * for all Firefox-based browsers newer than or equal 3, such as Camino,
   * Iceweasel, Seamonkey and Iceape.
   */
  IS_MT:
    (navigator.userAgent.indexOf('Firefox/') >= 0 &&
      navigator.userAgent.indexOf('Firefox/1.') < 0 &&
      navigator.userAgent.indexOf('Firefox/2.') < 0) ||
    (navigator.userAgent.indexOf('Iceweasel/') >= 0 &&
      navigator.userAgent.indexOf('Iceweasel/1.') < 0 &&
      navigator.userAgent.indexOf('Iceweasel/2.') < 0) ||
    (navigator.userAgent.indexOf('SeaMonkey/') >= 0 && navigator.userAgent.indexOf('SeaMonkey/1.') < 0) ||
    (navigator.userAgent.indexOf('Iceape/') >= 0 && navigator.userAgent.indexOf('Iceape/1.') < 0),

  /**
   * Variable: IS_VML
   *
   * True if the browser supports VML.
   */
  IS_VML: navigator.appName.toUpperCase() == 'MICROSOFT INTERNET EXPLORER',

  /**
   * Variable: IS_SVG
   *
   * True if the browser supports SVG.
   */
  IS_SVG: navigator.appName.toUpperCase() != 'MICROSOFT INTERNET EXPLORER',

  /**
   * Variable: NO_FO
   *
   * True if foreignObject support is not available. This is the case for
   * Opera, older SVG-based browsers and all versions of IE.
   */
  NO_FO:
    !document.createElementNS ||
    document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject') != '[object SVGForeignObjectElement]' ||
    navigator.userAgent.indexOf('Opera/') >= 0,

  /**
   * Variable: IS_WIN
   *
   * True if the client is a Windows.
   */
  IS_WIN: navigator.appVersion.indexOf('Win') > 0,

  /**
   * Variable: IS_MAC
   *
   * True if the client is a Mac.
   */
  IS_MAC: navigator.appVersion.indexOf('Mac') > 0,

  /**
   * Variable: IS_CHROMEOS
   *
   * True if the client is a Chrome OS.
   */
  IS_CHROMEOS: /\bCrOS\b/.test(navigator.appVersion),

  /**
   * Variable: IS_TOUCH
   *
   * True if this device supports touchstart/-move/-end events (Apple iOS,
   * Android, Chromebook and Chrome Browser on touch-enabled devices).
   */
  IS_TOUCH: 'ontouchstart' in document.documentElement,

  /**
   * Variable: IS_POINTER
   *
   * True if this device supports Microsoft pointer events (always false on Macs).
   */
  IS_POINTER: window.PointerEvent != null && !(navigator.appVersion.indexOf('Mac') > 0),

  /**
   * Variable: IS_LOCAL
   *
   * True if the documents location does not start with http:// or https://.
   */
  IS_LOCAL: document.location.href.indexOf('http://') < 0 && document.location.href.indexOf('https://') < 0,

  /**
   * Variable: defaultBundles
   *
   * Contains the base names of the default bundles if mxLoadResources is false.
   */
  defaultBundles: [],

  /**
   * Function: isBrowserSupported
   *
   * Returns true if the current browser is supported, that is, if
   * <mxClient.IS_VML> or <mxClient.IS_SVG> is true.
   *
   * Example:
   *
   * (code)
   * if (!mxClient.isBrowserSupported())
   * {
   *   mxUtils.error('Browser is not supported!', 200, false);
   * }
   * (end)
   */
  isBrowserSupported: function () {
    return mxClient.IS_VML || mxClient.IS_SVG;
  },

  /**
   * Function: link
   *
   * Adds a link node to the head of the document. Use this
   * to add a stylesheet to the page as follows:
   *
   * (code)
   * mxClient.link('stylesheet', filename);
   * (end)
   *
   * where filename is the (relative) URL of the stylesheet. The charset
   * is hardcoded to ISO-8859-1 and the type is text/css.
   *
   * Parameters:
   *
   * rel - String that represents the rel attribute of the link node.
   * href - String that represents the href attribute of the link node.
   * doc - Optional parent document of the link node.
   * id - unique id for the link element to check if it already exists
   */
  link: function (rel, href, doc, id) {
    doc = doc || document;

    // Workaround for Operation Aborted in IE6 if base tag is used in head
    if (mxClient.IS_IE6) {
      doc.write('<link rel="' + rel + '" href="' + href + '" charset="UTF-8" type="text/css"/>');
    } else {
      var link = doc.createElement('link');

      link.setAttribute('rel', rel);
      link.setAttribute('href', href);
      link.setAttribute('charset', 'UTF-8');
      link.setAttribute('type', 'text/css');

      if (id) {
        link.setAttribute('id', id);
      }

      var head = doc.getElementsByTagName('head')[0];
      head.appendChild(link);
    }
  },

  /**
   * Function: loadResources
   *
   * Helper method to load the default bundles if mxLoadResources is false.
   *
   * Parameters:
   *
   * fn - Function to call after all resources have been loaded.
   * lan - Optional string to pass to <mxResources.add>.
   */
  loadResources: function (fn, lan) {
    var pending = mxClient.defaultBundles.length;

    function callback() {
      if (--pending == 0) {
        fn();
      }
    }

    for (var i = 0; i < mxClient.defaultBundles.length; i++) {
      mxResources.add(mxClient.defaultBundles[i], lan, callback);
    }
  },

  /**
   * Function: include
   *
   * Dynamically adds a script node to the document header.
   *
   * In production environments, the includes are resolved in the mxClient.js
   * file to reduce the number of requests required for client startup. This
   * function should only be used in development environments, but not in
   * production systems.
   */
  include: function (src) {
    document.write('<script src="' + src + '"></script>');
  },
};

/**
 * Variable: mxLoadResources
 *
 * Optional global config variable to toggle loading of the two resource files
 * in <mxGraph> and <mxEditor>. Default is true. NOTE: This is a global variable,
 * not a variable of mxClient. If this is false, you can use <mxClient.loadResources>
 * with its callback to load the default bundles asynchronously.
 *
 * (code)
 * <script type="text/javascript">
 * 		var mxLoadResources = false;
 * </script>
 * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
 * (end)
 */
if (typeof mxLoadResources == 'undefined') {
  window.mxLoadResources = true;
}

/**
 * Variable: mxForceIncludes
 *
 * Optional global config variable to force loading the JavaScript files in
 * development mode. Default is undefined. NOTE: This is a global variable,
 * not a variable of mxClient.
 *
 * (code)
 * <script type="text/javascript">
 * 		var mxLoadResources = true;
 * </script>
 * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
 * (end)
 */
if (typeof mxForceIncludes == 'undefined') {
  window.mxForceIncludes = false;
}

/**
 * Variable: mxResourceExtension
 *
 * Optional global config variable to specify the extension of resource files.
 * Default is true. NOTE: This is a global variable, not a variable of mxClient.
 *
 * (code)
 * <script type="text/javascript">
 * 		var mxResourceExtension = '.txt';
 * </script>
 * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
 * (end)
 */
if (typeof mxResourceExtension == 'undefined') {
  window.mxResourceExtension = '.txt';
}

/**
 * Variable: mxLoadStylesheets
 *
 * Optional global config variable to toggle loading of the CSS files when
 * the library is initialized. Default is true. NOTE: This is a global variable,
 * not a variable of mxClient.
 *
 * (code)
 * <script type="text/javascript">
 * 		var mxLoadStylesheets = false;
 * </script>
 * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
 * (end)
 */
if (typeof mxLoadStylesheets == 'undefined') {
  window.mxLoadStylesheets = true;
}

/**
 * Variable: basePath
 *
 * Basepath for all URLs in the core without trailing slash. Default is '.'.
 * Set mxBasePath prior to loading the mxClient library as follows to override
 * this setting:
 *
 * (code)
 * <script type="text/javascript">
 * 		mxBasePath = '/path/to/core/directory';
 * </script>
 * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
 * (end)
 *
 * When using a relative path, the path is relative to the URL of the page that
 * contains the assignment. Trailing slashes are automatically removed.
 */
// if (typeof mxBasePath != 'undefined' && mxBasePath.length > 0) {
//   // Adds a trailing slash if required
//   if (mxBasePath.substring(mxBasePath.length - 1) == '/') {
//     mxBasePath = mxBasePath.substring(0, mxBasePath.length - 1);
//   }

//   mxClient.basePath = mxBasePath;
// } else {
//   mxClient.basePath = '.';
// }

mxClient.basePath = window.mxBasePath || '.';

/**
 * Variable: imageBasePath
 *
 * Basepath for all images URLs in the core without trailing slash. Default is
 * <mxClient.basePath> + '/images'. Set mxImageBasePath prior to loading the
 * mxClient library as follows to override this setting:
 *
 * (code)
 * <script type="text/javascript">
 * 		mxImageBasePath = '/path/to/image/directory';
 * </script>
 * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
 * (end)
 *
 * When using a relative path, the path is relative to the URL of the page that
 * contains the assignment. Trailing slashes are automatically removed.
 */
if (typeof mxImageBasePath != 'undefined' && mxImageBasePath.length > 0) {
  // Adds a trailing slash if required
  if (mxImageBasePath.substring(mxImageBasePath.length - 1) == '/') {
    mxImageBasePath = mxImageBasePath.substring(0, mxImageBasePath.length - 1);
  }

  mxClient.imageBasePath = mxImageBasePath;
} else {
  mxClient.imageBasePath = mxClient.basePath + '/images';
}

/**
 * Variable: language
 *
 * Defines the language of the client, eg. en for english, de for german etc.
 * The special value 'none' will disable all built-in internationalization and
 * resource loading. See <mxResources.getSpecialBundle> for handling identifiers
 * with and without a dash.
 *
 * Set mxLanguage prior to loading the mxClient library as follows to override
 * this setting:
 *
 * (code)
 * <script type="text/javascript">
 * 		mxLanguage = 'en';
 * </script>
 * <script type="text/javascript" src="js/mxClient.js"></script>
 * (end)
 *
 * If internationalization is disabled, then the following variables should be
 * overridden to reflect the current language of the system. These variables are
 * cleared when i18n is disabled.
 * <mxEditor.askZoomResource>, <mxEditor.lastSavedResource>,
 * <mxEditor.currentFileResource>, <mxEditor.propertiesResource>,
 * <mxEditor.tasksResource>, <mxEditor.helpResource>, <mxEditor.outlineResource>,
 * <mxElbowEdgeHandler.doubleClickOrientationResource>, <mxUtils.errorResource>,
 * <mxUtils.closeResource>, <mxGraphSelectionModel.doneResource>,
 * <mxGraphSelectionModel.updatingSelectionResource>, <mxGraphView.doneResource>,
 * <mxGraphView.updatingDocumentResource>, <mxCellRenderer.collapseExpandResource>,
 * <mxGraph.containsValidationErrorsResource> and
 * <mxGraph.alreadyConnectedResource>.
 */
if (typeof mxLanguage != 'undefined' && mxLanguage != null) {
  mxClient.language = mxLanguage;
} else {
  mxClient.language = mxClient.IS_IE ? navigator.userLanguage : navigator.language;
}

/**
 * Variable: defaultLanguage
 *
 * Defines the default language which is used in the common resource files. Any
 * resources for this language will only load the common resource file, but not
 * the language-specific resource file. Default is 'en'.
 *
 * Set mxDefaultLanguage prior to loading the mxClient library as follows to override
 * this setting:
 *
 * (code)
 * <script type="text/javascript">
 * 		mxDefaultLanguage = 'de';
 * </script>
 * <script type="text/javascript" src="js/mxClient.js"></script>
 * (end)
 */
if (typeof mxDefaultLanguage != 'undefined' && mxDefaultLanguage != null) {
  mxClient.defaultLanguage = mxDefaultLanguage;
} else {
  mxClient.defaultLanguage = 'en';
}

// Adds all required stylesheets and namespaces
// if (mxLoadStylesheets) {
//   mxClient.link('stylesheet', mxClient.basePath + '/css/common.css');
// }

/**
 * Variable: languages
 *
 * Defines the optional array of all supported language extensions. The default
 * language does not have to be part of this list. See
 * <mxResources.isLanguageSupported>.
 *
 * (code)
 * <script type="text/javascript">
 * 		mxLanguages = ['de', 'it', 'fr'];
 * </script>
 * <script type="text/javascript" src="js/mxClient.js"></script>
 * (end)
 *
 * This is used to avoid unnecessary requests to language files, ie. if a 404
 * will be returned.
 */
if (typeof mxLanguages != 'undefined' && mxLanguages != null) {
  mxClient.languages = mxLanguages;
}

// Adds required namespaces, stylesheets and memory handling for older IE browsers
if (mxClient.IS_VML) {
  if (mxClient.IS_SVG) {
    mxClient.IS_VML = false;
  } else {
    // Enables support for IE8 standards mode. Note that this requires all attributes for VML
    // elements to be set using direct notation, ie. node.attr = value, not setAttribute.
    if (document.namespaces != null) {
      if (document.documentMode == 8) {
        document.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml', '#default#VML');
        document.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office', '#default#VML');
      } else {
        document.namespaces.add(mxClient.VML_PREFIX, 'urn:schemas-microsoft-com:vml');
        document.namespaces.add(mxClient.OFFICE_PREFIX, 'urn:schemas-microsoft-com:office:office');
      }
    }

    // Workaround for limited number of stylesheets in IE (does not work in standards mode)
    if (mxClient.IS_QUIRKS && document.styleSheets.length >= 30) {
      (function () {
        var node = document.createElement('style');
        node.type = 'text/css';
        node.styleSheet.cssText =
          mxClient.VML_PREFIX +
          '\\:*{behavior:url(#default#VML)}' +
          mxClient.OFFICE_PREFIX +
          '\\:*{behavior:url(#default#VML)}';
        document.getElementsByTagName('head')[0].appendChild(node);
      })();
    } else {
      document.createStyleSheet().cssText =
        mxClient.VML_PREFIX +
        '\\:*{behavior:url(#default#VML)}' +
        mxClient.OFFICE_PREFIX +
        '\\:*{behavior:url(#default#VML)}';
    }
  }
}
/**
 * util/mxLog.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxLog = {
  /**
   * Class: mxLog
   *
   * A singleton class that implements a simple console.
   *
   * Variable: consoleName
   *
   * Specifies the name of the console window. Default is 'Console'.
   */
  consoleName: 'Console',

  /**
   * Variable: TRACE
   *
   * Specified if the output for <enter> and <leave> should be visible in the
   * console. Default is false.
   */
  TRACE: false,

  /**
   * Variable: DEBUG
   *
   * Specifies if the output for <debug> should be visible in the console.
   * Default is true.
   */
  DEBUG: true,

  /**
   * Variable: WARN
   *
   * Specifies if the output for <warn> should be visible in the console.
   * Default is true.
   */
  WARN: true,

  /**
   * Variable: buffer
   *
   * Buffer for pre-initialized content.
   */
  buffer: '',

  /**
   * Function: init
   *
   * Initializes the DOM node for the console. This requires document.body to
   * point to a non-null value. This is called from within <setVisible> if the
   * log has not yet been initialized.
   */
  init: function () {
    if (mxLog.window == null && document.body != null) {
      var title = mxLog.consoleName + ' - mxGraph ' + mxClient.VERSION;

      // Creates a table that maintains the layout
      var table = document.createElement('table');
      table.setAttribute('width', '100%');
      table.setAttribute('height', '100%');

      var tbody = document.createElement('tbody');
      var tr = document.createElement('tr');
      var td = document.createElement('td');
      td.style.verticalAlign = 'top';

      // Adds the actual console as a textarea
      mxLog.textarea = document.createElement('textarea');
      mxLog.textarea.setAttribute('wrap', 'off');
      mxLog.textarea.setAttribute('readOnly', 'true');
      mxLog.textarea.style.height = '100%';
      mxLog.textarea.style.resize = 'none';
      mxLog.textarea.value = mxLog.buffer;

      // Workaround for wrong width in standards mode
      if (mxClient.IS_NS && document.compatMode != 'BackCompat') {
        mxLog.textarea.style.width = '99%';
      } else {
        mxLog.textarea.style.width = '100%';
      }

      td.appendChild(mxLog.textarea);
      tr.appendChild(td);
      tbody.appendChild(tr);

      // Creates the container div
      tr = document.createElement('tr');
      mxLog.td = document.createElement('td');
      mxLog.td.style.verticalAlign = 'top';
      mxLog.td.setAttribute('height', '30px');

      tr.appendChild(mxLog.td);
      tbody.appendChild(tr);
      table.appendChild(tbody);

      // Adds various debugging buttons
      mxLog.addButton('Info', function (evt) {
        mxLog.info();
      });

      mxLog.addButton('DOM', function (evt) {
        var content = mxUtils.getInnerHtml(document.body);
        mxLog.debug(content);
      });

      mxLog.addButton('Trace', function (evt) {
        mxLog.TRACE = !mxLog.TRACE;

        if (mxLog.TRACE) {
          mxLog.debug('Tracing enabled');
        } else {
          mxLog.debug('Tracing disabled');
        }
      });

      mxLog.addButton('Copy', function (evt) {
        try {
          mxUtils.copy(mxLog.textarea.value);
        } catch (err) {
          mxUtils.alert(err);
        }
      });

      mxLog.addButton('Show', function (evt) {
        try {
          mxUtils.popup(mxLog.textarea.value);
        } catch (err) {
          mxUtils.alert(err);
        }
      });

      mxLog.addButton('Clear', function (evt) {
        mxLog.textarea.value = '';
      });

      // Cross-browser code to get window size
      var h = 0;
      var w = 0;

      if (typeof window.innerWidth === 'number') {
        h = window.innerHeight;
        w = window.innerWidth;
      } else {
        h = document.documentElement.clientHeight || document.body.clientHeight;
        w = document.body.clientWidth;
      }

      mxLog.window = new mxWindow(title, table, Math.max(0, w - 320), Math.max(0, h - 210), 300, 160);
      mxLog.window.setMaximizable(true);
      mxLog.window.setScrollable(false);
      mxLog.window.setResizable(true);
      mxLog.window.setClosable(true);
      mxLog.window.destroyOnClose = false;

      // Workaround for ignored textarea height in various setups
      if (
        ((mxClient.IS_NS || mxClient.IS_IE) &&
          !mxClient.IS_GC &&
          !mxClient.IS_SF &&
          document.compatMode != 'BackCompat') ||
        document.documentMode == 11
      ) {
        var elt = mxLog.window.getElement();

        var resizeHandler = function (sender, evt) {
          mxLog.textarea.style.height = Math.max(0, elt.offsetHeight - 70) + 'px';
        };

        mxLog.window.addListener(mxEvent.RESIZE_END, resizeHandler);
        mxLog.window.addListener(mxEvent.MAXIMIZE, resizeHandler);
        mxLog.window.addListener(mxEvent.NORMALIZE, resizeHandler);

        mxLog.textarea.style.height = '92px';
      }
    }
  },

  /**
   * Function: info
   *
   * Writes the current navigator information to the console.
   */
  info: function () {
    mxLog.writeln(mxUtils.toString(navigator));
  },

  /**
   * Function: addButton
   *
   * Adds a button to the console using the given label and function.
   */
  addButton: function (lab, funct) {
    var button = document.createElement('button');
    mxUtils.write(button, lab);
    mxEvent.addListener(button, 'click', funct);
    mxLog.td.appendChild(button);
  },

  /**
   * Function: isVisible
   *
   * Returns true if the console is visible.
   */
  isVisible: function () {
    if (mxLog.window != null) {
      return mxLog.window.isVisible();
    }

    return false;
  },

  /**
   * Function: show
   *
   * Shows the console.
   */
  show: function () {
    mxLog.setVisible(true);
  },

  /**
   * Function: setVisible
   *
   * Shows or hides the console.
   */
  setVisible: function (visible) {
    if (mxLog.window == null) {
      mxLog.init();
    }

    if (mxLog.window != null) {
      mxLog.window.setVisible(visible);
    }
  },

  /**
   * Function: enter
   *
   * Writes the specified string to the console
   * if <TRACE> is true and returns the current
   * time in milliseconds.
   *
   * Example:
   *
   * (code)
   * mxLog.show();
   * var t0 = mxLog.enter('Hello');
   * // Do something
   * mxLog.leave('World!', t0);
   * (end)
   */
  enter: function (string) {
    if (mxLog.TRACE) {
      mxLog.writeln('Entering ' + string);

      return new Date().getTime();
    }
  },

  /**
   * Function: leave
   *
   * Writes the specified string to the console
   * if <TRACE> is true and computes the difference
   * between the current time and t0 in milliseconds.
   * See <enter> for an example.
   */
  leave: function (string, t0) {
    if (mxLog.TRACE) {
      var dt = t0 != 0 ? ' (' + (new Date().getTime() - t0) + ' ms)' : '';
      mxLog.writeln('Leaving ' + string + dt);
    }
  },

  /**
   * Function: debug
   *
   * Adds all arguments to the console if <DEBUG> is enabled.
   *
   * Example:
   *
   * (code)
   * mxLog.show();
   * mxLog.debug('Hello, World!');
   * (end)
   */
  debug: function () {
    if (mxLog.DEBUG) {
      mxLog.writeln.apply(this, arguments);
    }
  },

  /**
   * Function: warn
   *
   * Adds all arguments to the console if <WARN> is enabled.
   *
   * Example:
   *
   * (code)
   * mxLog.show();
   * mxLog.warn('Hello, World!');
   * (end)
   */
  warn: function () {
    if (mxLog.WARN) {
      mxLog.writeln.apply(this, arguments);
    }
  },

  /**
   * Function: write
   *
   * Adds the specified strings to the console.
   */
  write: function () {
    var string = '';

    for (var i = 0; i < arguments.length; i++) {
      string += arguments[i];

      if (i < arguments.length - 1) {
        string += ' ';
      }
    }

    if (mxLog.textarea != null) {
      mxLog.textarea.value = mxLog.textarea.value + string;

      // Workaround for no update in Presto 2.5.22 (Opera 10.5)
      if (navigator.userAgent != null && navigator.userAgent.indexOf('Presto/2.5') >= 0) {
        mxLog.textarea.style.visibility = 'hidden';
        mxLog.textarea.style.visibility = 'visible';
      }

      mxLog.textarea.scrollTop = mxLog.textarea.scrollHeight;
    } else {
      mxLog.buffer += string;
    }
  },

  /**
   * Function: writeln
   *
   * Adds the specified strings to the console, appending a linefeed at the
   * end of each string.
   */
  writeln: function () {
    var string = '';

    for (var i = 0; i < arguments.length; i++) {
      string += arguments[i];

      if (i < arguments.length - 1) {
        string += ' ';
      }
    }

    mxLog.write(string + '\n');
  },
};

/**
 * util/mxObjectIdentity.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxObjectIdentity = {
  /**
   * Class: mxObjectIdentity
   *
   * Identity for JavaScript objects and functions. This is implemented using
   * a simple incrementing counter which is stored in each object under
   * <FIELD_NAME>.
   *
   * The identity for an object does not change during its lifecycle.
   *
   * Variable: FIELD_NAME
   *
   * Name of the field to be used to store the object ID. Default is
   * <code>mxObjectId</code>.
   */
  FIELD_NAME: 'mxObjectId',

  /**
   * Variable: counter
   *
   * Current counter.
   */
  counter: 0,

  /**
   * Function: get
   *
   * Returns the ID for the given object or function or null if no object
   * is specified.
   */
  get: function (obj) {
    if (obj != null) {
      if (obj[mxObjectIdentity.FIELD_NAME] == null) {
        if (typeof obj === 'object') {
          var ctor = mxUtils.getFunctionName(obj.constructor);
          obj[mxObjectIdentity.FIELD_NAME] = ctor + '#' + mxObjectIdentity.counter++;
        } else if (typeof obj === 'function') {
          obj[mxObjectIdentity.FIELD_NAME] = 'Function#' + mxObjectIdentity.counter++;
        }
      }

      return obj[mxObjectIdentity.FIELD_NAME];
    }

    return null;
  },

  /**
   * Function: clear
   *
   * Deletes the ID from the given object or function.
   */
  clear: function (obj) {
    if (typeof obj === 'object' || typeof obj === 'function') {
      delete obj[mxObjectIdentity.FIELD_NAME];
    }
  },
};

/**
 * util/mxDictionary.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDictionary
 *
 * A wrapper class for an associative array with object keys. Note: This
 * implementation uses <mxObjectIdentitiy> to turn object keys into strings.
 *
 * Constructor: mxEventSource
 *
 * Constructs a new dictionary which allows object to be used as keys.
 */
function mxDictionary() {
  this.clear();
}

/**
 * Function: map
 *
 * Stores the (key, value) pairs in this dictionary.
 */
mxDictionary.prototype.map = null;

/**
 * Function: clear
 *
 * Clears the dictionary.
 */
mxDictionary.prototype.clear = function () {
  this.map = {};
};

/**
 * Function: get
 *
 * Returns the value for the given key.
 */
mxDictionary.prototype.get = function (key) {
  var id = mxObjectIdentity.get(key);

  return this.map[id];
};

/**
 * Function: put
 *
 * Stores the value under the given key and returns the previous
 * value for that key.
 */
mxDictionary.prototype.put = function (key, value) {
  var id = mxObjectIdentity.get(key);
  var previous = this.map[id];
  this.map[id] = value;

  return previous;
};

/**
 * Function: remove
 *
 * Removes the value for the given key and returns the value that
 * has been removed.
 */
mxDictionary.prototype.remove = function (key) {
  var id = mxObjectIdentity.get(key);
  var previous = this.map[id];
  delete this.map[id];

  return previous;
};

/**
 * Function: getKeys
 *
 * Returns all keys as an array.
 */
mxDictionary.prototype.getKeys = function () {
  var result = [];

  for (var key in this.map) {
    result.push(key);
  }

  return result;
};

/**
 * Function: getValues
 *
 * Returns all values as an array.
 */
mxDictionary.prototype.getValues = function () {
  var result = [];

  for (var key in this.map) {
    result.push(this.map[key]);
  }

  return result;
};

/**
 * Function: visit
 *
 * Visits all entries in the dictionary using the given function with the
 * following signature: function(key, value) where key is a string and
 * value is an object.
 *
 * Parameters:
 *
 * visitor - A function that takes the key and value as arguments.
 */
mxDictionary.prototype.visit = function (visitor) {
  for (var key in this.map) {
    visitor(key, this.map[key]);
  }
};

/**
 * util/mxResources.js
 */
/**
 * Copyright (c) 2006-2016, JGraph Ltd
 * Copyright (c) 2006-2016, Gaudenz Alder
 */
var mxResources = {
  /**
   * Class: mxResources
   *
   * Implements internationalization. You can provide any number of
   * resource files on the server using the following format for the
   * filename: name[-en].properties. The en stands for any lowercase
   * 2-character language shortcut (eg. de for german, fr for french).
   *
   * If the optional language extension is omitted, then the file is used as a
   * default resource which is loaded in all cases. If a properties file for a
   * specific language exists, then it is used to override the settings in the
   * default resource. All entries in the file are of the form key=value. The
   * values may then be accessed in code via <get>. Lines without
   * equal signs in the properties files are ignored.
   *
   * Resource files may either be added programmatically using
   * <add> or via a resource tag in the UI section of the
   * editor configuration file, eg:
   *
   * (code)
   * <mxEditor>
   *   <ui>
   *     <resource basename="examples/resources/mxWorkflow"/>
   * (end)
   *
   * The above element will load examples/resources/mxWorkflow.properties as well
   * as the language specific file for the current language, if it exists.
   *
   * Values may contain placeholders of the form {1}...{n} where each placeholder
   * is replaced with the value of the corresponding array element in the params
   * argument passed to <mxResources.get>. The placeholder {1} maps to the first
   * element in the array (at index 0).
   *
   * See <mxClient.language> for more information on specifying the default
   * language or disabling all loading of resources.
   *
   * Lines that start with a # sign will be ignored.
   *
   * Special characters
   *
   * To use unicode characters, use the standard notation (eg. \u8fd1) or %u as a
   * prefix (eg. %u20AC will display a Euro sign). For normal hex encoded strings,
   * use % as a prefix, eg. %F6 will display a "o umlaut" (&ouml;).
   *
   * See <resourcesEncoded> to disable this. If you disable this, make sure that
   * your files are UTF-8 encoded.
   *
   * Asynchronous loading
   *
   * By default, the core adds two resource files synchronously at load time.
   * To load these files asynchronously, set <mxLoadResources> to false
   * before loading mxClient.js and use <mxResources.loadResources> instead.
   *
   * Variable: resources
   *
   * Object that maps from keys to values.
   */
  resources: {},

  /**
   * Variable: extension
   *
   * Specifies the extension used for language files. Default is <mxResourceExtension>.
   */
  extension: mxResourceExtension,

  /**
   * Variable: resourcesEncoded
   *
   * Specifies whether or not values in resource files are encoded with \u or
   * percentage. Default is false.
   */
  resourcesEncoded: false,

  /**
   * Variable: loadDefaultBundle
   *
   * Specifies if the default file for a given basename should be loaded.
   * Default is true.
   */
  loadDefaultBundle: true,

  /**
   * Variable: loadDefaultBundle
   *
   * Specifies if the specific language file file for a given basename should
   * be loaded. Default is true.
   */
  loadSpecialBundle: true,

  /**
   * Function: isLanguageSupported
   *
   * Hook for subclassers to disable support for a given language. This
   * implementation returns true if lan is in <mxClient.languages>.
   *
   * Parameters:
   *
   * lan - The current language.
   */
  isLanguageSupported: function (lan) {
    if (mxClient.languages != null) {
      return mxUtils.indexOf(mxClient.languages, lan) >= 0;
    }

    return true;
  },

  /**
   * Function: getDefaultBundle
   *
   * Hook for subclassers to return the URL for the special bundle. This
   * implementation returns basename + <extension> or null if
   * <loadDefaultBundle> is false.
   *
   * Parameters:
   *
   * basename - The basename for which the file should be loaded.
   * lan - The current language.
   */
  getDefaultBundle: function (basename, lan) {
    if (mxResources.loadDefaultBundle || !mxResources.isLanguageSupported(lan)) {
      return basename + mxResources.extension;
    } else {
      return null;
    }
  },

  /**
   * Function: getSpecialBundle
   *
   * Hook for subclassers to return the URL for the special bundle. This
   * implementation returns basename + '_' + lan + <extension> or null if
   * <loadSpecialBundle> is false or lan equals <mxClient.defaultLanguage>.
   *
   * If <mxResources.languages> is not null and <mxClient.language> contains
   * a dash, then this method checks if <isLanguageSupported> returns true
   * for the full language (including the dash). If that returns false the
   * first part of the language (up to the dash) will be tried as an extension.
   *
   * If <mxResources.language> is null then the first part of the language is
   * used to maintain backwards compatibility.
   *
   * Parameters:
   *
   * basename - The basename for which the file should be loaded.
   * lan - The language for which the file should be loaded.
   */
  getSpecialBundle: function (basename, lan) {
    if (mxClient.languages == null || !this.isLanguageSupported(lan)) {
      var dash = lan.indexOf('-');

      if (dash > 0) {
        lan = lan.substring(0, dash);
      }
    }

    if (mxResources.loadSpecialBundle && mxResources.isLanguageSupported(lan) && lan != mxClient.defaultLanguage) {
      return basename + '_' + lan + mxResources.extension;
    } else {
      return null;
    }
  },

  /**
   * Function: add
   *
   * Adds the default and current language properties file for the specified
   * basename. Existing keys are overridden as new files are added. If no
   * callback is used then the request is synchronous.
   *
   * Example:
   *
   * At application startup, additional resources may be
   * added using the following code:
   *
   * (code)
   * mxResources.add('resources/editor');
   * (end)
   *
   * Parameters:
   *
   * basename - The basename for which the file should be loaded.
   * lan - The language for which the file should be loaded.
   * callback - Optional callback for asynchronous loading.
   */
  add: function (basename, lan, callback) {
    lan = lan != null ? lan : mxClient.language != null ? mxClient.language.toLowerCase() : mxConstants.NONE;

    if (lan != mxConstants.NONE) {
      var defaultBundle = mxResources.getDefaultBundle(basename, lan);
      var specialBundle = mxResources.getSpecialBundle(basename, lan);

      var loadSpecialBundle = function () {
        if (specialBundle != null) {
          if (callback) {
            mxUtils.get(
              specialBundle,
              function (req) {
                mxResources.parse(req.getText());
                callback();
              },
              function () {
                callback();
              }
            );
          } else {
            try {
              var req = mxUtils.load(specialBundle);

              if (req.isReady()) {
                mxResources.parse(req.getText());
              }
            } catch (e) {
              // ignore
            }
          }
        } else if (callback != null) {
          callback();
        }
      };

      if (defaultBundle != null) {
        if (callback) {
          mxUtils.get(
            defaultBundle,
            function (req) {
              mxResources.parse(req.getText());
              loadSpecialBundle();
            },
            function () {
              loadSpecialBundle();
            }
          );
        } else {
          try {
            var req = mxUtils.load(defaultBundle);

            if (req.isReady()) {
              mxResources.parse(req.getText());
            }

            loadSpecialBundle();
          } catch (e) {
            // ignore
          }
        }
      } else {
        // Overlays the language specific file (_lan-extension)
        loadSpecialBundle();
      }
    }
  },

  /**
   * Function: parse
   *
   * Parses the key, value pairs in the specified
   * text and stores them as local resources.
   */
  parse: function (text) {
    if (text != null) {
      var lines = text.split('\n');

      for (var i = 0; i < lines.length; i++) {
        if (lines[i].charAt(0) != '#') {
          var index = lines[i].indexOf('=');

          if (index > 0) {
            var key = lines[i].substring(0, index);
            var idx = lines[i].length;

            if (lines[i].charCodeAt(idx - 1) == 13) {
              idx--;
            }

            var value = lines[i].substring(index + 1, idx);

            if (this.resourcesEncoded) {
              value = value.replace(/\\(?=u[a-fA-F\d]{4})/g, '%');
              mxResources.resources[key] = unescape(value);
            } else {
              mxResources.resources[key] = value;
            }
          }
        }
      }
    }
  },

  /**
   * Function: get
   *
   * Returns the value for the specified resource key.
   *
   * Example:
   * To read the value for 'welomeMessage', use the following:
   * (code)
   * var result = mxResources.get('welcomeMessage') || '';
   * (end)
   *
   * This would require an entry of the following form in
   * one of the English language resource files:
   * (code)
   * welcomeMessage=Welcome to mxGraph!
   * (end)
   *
   * The part behind the || is the string value to be used if the given
   * resource is not available.
   *
   * Parameters:
   *
   * key - String that represents the key of the resource to be returned.
   * params - Array of the values for the placeholders of the form {1}...{n}
   * to be replaced with in the resulting string.
   * defaultValue - Optional string that specifies the default return value.
   */
  get: function (key, params, defaultValue) {
    var value = mxResources.resources[key];

    // Applies the default value if no resource was found
    if (value == null) {
      value = defaultValue;
    }

    // Replaces the placeholders with the values in the array
    if (value != null && params != null) {
      value = mxResources.replacePlaceholders(value, params);
    }

    return value;
  },

  /**
   * Function: replacePlaceholders
   *
   * Replaces the given placeholders with the given parameters.
   *
   * Parameters:
   *
   * value - String that contains the placeholders.
   * params - Array of the values for the placeholders of the form {1}...{n}
   * to be replaced with in the resulting string.
   */
  replacePlaceholders: function (value, params) {
    var result = [];
    var index = null;

    for (var i = 0; i < value.length; i++) {
      var c = value.charAt(i);

      if (c == '{') {
        index = '';
      } else if (index != null && c == '}') {
        index = parseInt(index) - 1;

        if (index >= 0 && index < params.length) {
          result.push(params[index]);
        }

        index = null;
      } else if (index != null) {
        index += c;
      } else {
        result.push(c);
      }
    }

    return result.join('');
  },

  /**
   * Function: loadResources
   *
   * Loads all required resources asynchronously. Use this to load the graph and
   * editor resources if <mxLoadResources> is false.
   *
   * Parameters:
   *
   * callback - Callback function for asynchronous loading.
   */
  loadResources: function (callback) {
    mxResources.add(mxClient.basePath + '/resources/graph', null, function () {});
  },
};

/**
 * util/mxPoint.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPoint
 *
 * Implements a 2-dimensional vector with double precision coordinates.
 *
 * Constructor: mxPoint
 *
 * Constructs a new point for the optional x and y coordinates. If no
 * coordinates are given, then the default values for <x> and <y> are used.
 */
function mxPoint(x, y) {
  this.x = x != null ? x : 0;
  this.y = y != null ? y : 0;
}

/**
 * Variable: x
 *
 * Holds the x-coordinate of the point. Default is 0.
 */
mxPoint.prototype.x = null;

/**
 * Variable: y
 *
 * Holds the y-coordinate of the point. Default is 0.
 */
mxPoint.prototype.y = null;

/**
 * Function: equals
 *
 * Returns true if the given object equals this point.
 */
mxPoint.prototype.equals = function (obj) {
  return obj != null && obj.x == this.x && obj.y == this.y;
};

/**
 * Function: clone
 *
 * Returns a clone of this <mxPoint>.
 */
mxPoint.prototype.clone = function () {
  // Handles subclasses as well
  return mxUtils.clone(this);
};

/**
 * util/mxRectangle.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxRectangle
 *
 * Extends <mxPoint> to implement a 2-dimensional rectangle with double
 * precision coordinates.
 *
 * Constructor: mxRectangle
 *
 * Constructs a new rectangle for the optional parameters. If no parameters
 * are given then the respective default values are used.
 */
function mxRectangle(x, y, width, height) {
  mxPoint.call(this, x, y);

  this.width = width != null ? width : 0;
  this.height = height != null ? height : 0;
}

/**
 * Extends mxPoint.
 */
mxRectangle.prototype = new mxPoint();
mxRectangle.prototype.constructor = mxRectangle;

/**
 * Variable: width
 *
 * Holds the width of the rectangle. Default is 0.
 */
mxRectangle.prototype.width = null;

/**
 * Variable: height
 *
 * Holds the height of the rectangle. Default is 0.
 */
mxRectangle.prototype.height = null;

/**
 * Function: setRect
 *
 * Sets this rectangle to the specified values
 */
mxRectangle.prototype.setRect = function (x, y, w, h) {
  this.x = x;
  this.y = y;
  this.width = w;
  this.height = h;
};

/**
 * Function: getCenterX
 *
 * Returns the x-coordinate of the center point.
 */
mxRectangle.prototype.getCenterX = function () {
  return this.x + this.width / 2;
};

/**
 * Function: getCenterY
 *
 * Returns the y-coordinate of the center point.
 */
mxRectangle.prototype.getCenterY = function () {
  return this.y + this.height / 2;
};

/**
 * Function: add
 *
 * Adds the given rectangle to this rectangle.
 */
mxRectangle.prototype.add = function (rect) {
  if (rect != null) {
    var minX = Math.min(this.x, rect.x);
    var minY = Math.min(this.y, rect.y);
    var maxX = Math.max(this.x + this.width, rect.x + rect.width);
    var maxY = Math.max(this.y + this.height, rect.y + rect.height);

    this.x = minX;
    this.y = minY;
    this.width = maxX - minX;
    this.height = maxY - minY;
  }
};

/**
 * Function: intersect
 *
 * Changes this rectangle to where it overlaps with the given rectangle.
 */
mxRectangle.prototype.intersect = function (rect) {
  if (rect != null) {
    var r1 = this.x + this.width;
    var r2 = rect.x + rect.width;

    var b1 = this.y + this.height;
    var b2 = rect.y + rect.height;

    this.x = Math.max(this.x, rect.x);
    this.y = Math.max(this.y, rect.y);
    this.width = Math.min(r1, r2) - this.x;
    this.height = Math.min(b1, b2) - this.y;
  }
};

/**
 * Function: grow
 *
 * Grows the rectangle by the given amount, that is, this method subtracts
 * the given amount from the x- and y-coordinates and adds twice the amount
 * to the width and height.
 */
mxRectangle.prototype.grow = function (amount) {
  this.x -= amount;
  this.y -= amount;
  this.width += 2 * amount;
  this.height += 2 * amount;

  return this;
};

/**
 * Function: getPoint
 *
 * Returns the top, left corner as a new <mxPoint>.
 */
mxRectangle.prototype.getPoint = function () {
  return new mxPoint(this.x, this.y);
};

/**
 * Function: rotate90
 *
 * Rotates this rectangle by 90 degree around its center point.
 */
mxRectangle.prototype.rotate90 = function () {
  var t = (this.width - this.height) / 2;
  this.x += t;
  this.y -= t;
  var tmp = this.width;
  this.width = this.height;
  this.height = tmp;
};

/**
 * Function: equals
 *
 * Returns true if the given object equals this rectangle.
 */
mxRectangle.prototype.equals = function (obj) {
  return obj != null && obj.x == this.x && obj.y == this.y && obj.width == this.width && obj.height == this.height;
};

/**
 * Function: fromRectangle
 *
 * Returns a new <mxRectangle> which is a copy of the given rectangle.
 */
mxRectangle.fromRectangle = function (rect) {
  return new mxRectangle(rect.x, rect.y, rect.width, rect.height);
};

/**
 * util/mxEffects.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxEffects = {
  /**
   * Class: mxEffects
   *
   * Provides animation effects.
   */

  /**
   * Function: animateChanges
   *
   * Asynchronous animated move operation. See also: <mxMorphing>.
   *
   * Example:
   *
   * (code)
   * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
   * {
   *   var changes = evt.getProperty('edit').changes;
   *
   *   if (changes.length < 10)
   *   {
   *     mxEffects.animateChanges(graph, changes);
   *   }
   * });
   * (end)
   *
   * Parameters:
   *
   * graph - <mxGraph> that received the changes.
   * changes - Array of changes to be animated.
   * done - Optional function argument that is invoked after the
   * last step of the animation.
   */
  animateChanges: function (graph, changes, done) {
    var maxStep = 10;
    var step = 0;

    var animate = function () {
      var isRequired = false;

      for (var i = 0; i < changes.length; i++) {
        var change = changes[i];

        if (
          change instanceof mxGeometryChange ||
          change instanceof mxTerminalChange ||
          change instanceof mxValueChange ||
          change instanceof mxChildChange ||
          change instanceof mxStyleChange
        ) {
          var state = graph.getView().getState(change.cell || change.child, false);

          if (state != null) {
            isRequired = true;

            if (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell)) {
              mxUtils.setOpacity(state.shape.node, (100 * step) / maxStep);
            } else {
              var scale = graph.getView().scale;

              var dx = (change.geometry.x - change.previous.x) * scale;
              var dy = (change.geometry.y - change.previous.y) * scale;

              var sx = (change.geometry.width - change.previous.width) * scale;
              var sy = (change.geometry.height - change.previous.height) * scale;

              if (step == 0) {
                state.x -= dx;
                state.y -= dy;
                state.width -= sx;
                state.height -= sy;
              } else {
                state.x += dx / maxStep;
                state.y += dy / maxStep;
                state.width += sx / maxStep;
                state.height += sy / maxStep;
              }

              graph.cellRenderer.redraw(state);

              // Fades all connected edges and children
              mxEffects.cascadeOpacity(graph, change.cell, (100 * step) / maxStep);
            }
          }
        }
      }

      if (step < maxStep && isRequired) {
        step++;
        window.setTimeout(animate, delay);
      } else if (done != null) {
        done();
      }
    };

    var delay = 30;
    animate();
  },

  /**
   * Function: cascadeOpacity
   *
   * Sets the opacity on the given cell and its descendants.
   *
   * Parameters:
   *
   * graph - <mxGraph> that contains the cells.
   * cell - <mxCell> to set the opacity for.
   * opacity - New value for the opacity in %.
   */
  cascadeOpacity: function (graph, cell, opacity) {
    // Fades all children
    var childCount = graph.model.getChildCount(cell);

    for (var i = 0; i < childCount; i++) {
      var child = graph.model.getChildAt(cell, i);
      var childState = graph.getView().getState(child);

      if (childState != null) {
        mxUtils.setOpacity(childState.shape.node, opacity);
        mxEffects.cascadeOpacity(graph, child, opacity);
      }
    }

    // Fades all connected edges
    var edges = graph.model.getEdges(cell);

    if (edges != null) {
      for (var i = 0; i < edges.length; i++) {
        var edgeState = graph.getView().getState(edges[i]);

        if (edgeState != null) {
          mxUtils.setOpacity(edgeState.shape.node, opacity);
        }
      }
    }
  },

  /**
   * Function: fadeOut
   *
   * Asynchronous fade-out operation.
   */
  fadeOut: function (node, from, remove, step, delay, isEnabled) {
    step = step || 40;
    delay = delay || 30;

    var opacity = from || 100;

    mxUtils.setOpacity(node, opacity);

    if (isEnabled || isEnabled == null) {
      var f = function () {
        opacity = Math.max(opacity - step, 0);
        mxUtils.setOpacity(node, opacity);

        if (opacity > 0) {
          window.setTimeout(f, delay);
        } else {
          node.style.visibility = 'hidden';

          if (remove && node.parentNode) {
            node.parentNode.removeChild(node);
          }
        }
      };
      window.setTimeout(f, delay);
    } else {
      node.style.visibility = 'hidden';

      if (remove && node.parentNode) {
        node.parentNode.removeChild(node);
      }
    }
  },
};

/**
 * util/mxUtils.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxUtils = {
  /**
   * Class: mxUtils
   *
   * A singleton class that provides cross-browser helper methods.
   * This is a global functionality. To access the functions in this
   * class, use the global classname appended by the functionname.
   * You may have to load chrome://global/content/contentAreaUtils.js
   * to disable certain security restrictions in Mozilla for the <open>,
   * <save>, <saveAs> and <copy> function.
   *
   * For example, the following code displays an error message:
   *
   * (code)
   * mxUtils.error('Browser is not supported!', 200, false);
   * (end)
   *
   * Variable: errorResource
   *
   * Specifies the resource key for the title of the error window. If the
   * resource for this key does not exist then the value is used as
   * the title. Default is 'error'.
   */
  errorResource: mxClient.language != 'none' ? 'error' : '',

  /**
   * Variable: closeResource
   *
   * Specifies the resource key for the label of the close button. If the
   * resource for this key does not exist then the value is used as
   * the label. Default is 'close'.
   */
  closeResource: mxClient.language != 'none' ? 'close' : '',

  /**
   * Variable: errorImage
   *
   * Defines the image used for error dialogs.
   */
  errorImage: mxClient.imageBasePath + '/error.gif',

  /**
   * Function: removeCursors
   *
   * Removes the cursors from the style of the given DOM node and its
   * descendants.
   *
   * Parameters:
   *
   * element - DOM node to remove the cursor style from.
   */
  removeCursors: function (element) {
    if (element.style != null) {
      element.style.cursor = '';
    }

    var children = element.childNodes;

    if (children != null) {
      var childCount = children.length;

      for (var i = 0; i < childCount; i += 1) {
        mxUtils.removeCursors(children[i]);
      }
    }
  },

  /**
   * Function: getCurrentStyle
   *
   * Returns the current style of the specified element.
   *
   * Parameters:
   *
   * element - DOM node whose current style should be returned.
   */
  getCurrentStyle: (function () {
    if (mxClient.IS_IE && (document.documentMode == null || document.documentMode < 9)) {
      return function (element) {
        return element != null ? element.currentStyle : null;
      };
    } else {
      return function (element) {
        return element != null ? window.getComputedStyle(element, '') : null;
      };
    }
  })(),

  /**
   * Function: parseCssNumber
   *
   * Parses the given CSS numeric value adding handling for the values thin,
   * medium and thick (2, 4 and 6).
   */
  parseCssNumber: function (value) {
    if (value == 'thin') {
      value = '2';
    } else if (value == 'medium') {
      value = '4';
    } else if (value == 'thick') {
      value = '6';
    }

    value = parseFloat(value);

    if (isNaN(value)) {
      value = 0;
    }

    return value;
  },

  /**
   * Function: setPrefixedStyle
   *
   * Adds the given style with the standard name and an optional vendor prefix for the current
   * browser.
   *
   * (code)
   * mxUtils.setPrefixedStyle(node.style, 'transformOrigin', '0% 0%');
   * (end)
   */
  setPrefixedStyle: (function () {
    var prefix = null;

    if (mxClient.IS_OT) {
      prefix = 'O';
    } else if (mxClient.IS_SF || mxClient.IS_GC) {
      prefix = 'Webkit';
    } else if (mxClient.IS_MT) {
      prefix = 'Moz';
    } else if (mxClient.IS_IE && document.documentMode >= 9 && document.documentMode < 10) {
      prefix = 'ms';
    }

    return function (style, name, value) {
      style[name] = value;

      if (prefix != null && name.length > 0) {
        name = prefix + name.substring(0, 1).toUpperCase() + name.substring(1);
        style[name] = value;
      }
    };
  })(),

  /**
   * Function: hasScrollbars
   *
   * Returns true if the overflow CSS property of the given node is either
   * scroll or auto.
   *
   * Parameters:
   *
   * node - DOM node whose style should be checked for scrollbars.
   */
  hasScrollbars: function (node) {
    var style = mxUtils.getCurrentStyle(node);

    return style != null && (style.overflow == 'scroll' || style.overflow == 'auto');
  },

  /**
   * Function: bind
   *
   * Returns a wrapper function that locks the execution scope of the given
   * function to the specified scope. Inside funct, the "this" keyword
   * becomes a reference to that scope.
   */
  bind: function (scope, funct) {
    return function () {
      return funct.apply(scope, arguments);
    };
  },

  /**
   * Function: eval
   *
   * Evaluates the given expression using eval and returns the JavaScript
   * object that represents the expression result. Supports evaluation of
   * expressions that define functions and returns the function object for
   * these expressions.
   *
   * Parameters:
   *
   * expr - A string that represents a JavaScript expression.
   */
  eval: function (expr) {
    var result = null;

    if (expr.indexOf('function') >= 0) {
      try {
        eval('var _mxJavaScriptExpression=' + expr);
        result = _mxJavaScriptExpression;
        // TODO: Use delete here?
        _mxJavaScriptExpression = null;
      } catch (e) {
        mxLog.warn(e.message + ' while evaluating ' + expr);
      }
    } else {
      try {
        result = eval(expr);
      } catch (e) {
        mxLog.warn(e.message + ' while evaluating ' + expr);
      }
    }

    return result;
  },

  /**
   * Function: findNode
   *
   * Returns the first node where attr equals value.
   * This implementation does not use XPath.
   */
  findNode: function (node, attr, value) {
    if (node.nodeType == mxConstants.NODETYPE_ELEMENT) {
      var tmp = node.getAttribute(attr);

      if (tmp != null && tmp == value) {
        return node;
      }
    }

    node = node.firstChild;

    while (node != null) {
      var result = mxUtils.findNode(node, attr, value);

      if (result != null) {
        return result;
      }

      node = node.nextSibling;
    }

    return null;
  },

  /**
   * Function: getFunctionName
   *
   * Returns the name for the given function.
   *
   * Parameters:
   *
   * f - JavaScript object that represents a function.
   */
  getFunctionName: function (f) {
    var str = null;

    if (f != null) {
      if (f.name != null) {
        str = f.name;
      } else {
        str = mxUtils.trim(f.toString());

        if (/^function\s/.test(str)) {
          str = mxUtils.ltrim(str.substring(9));
          var idx2 = str.indexOf('(');

          if (idx2 > 0) {
            str = str.substring(0, idx2);
          }
        }
      }
    }

    return str;
  },

  /**
   * Function: indexOf
   *
   * Returns the index of obj in array or -1 if the array does not contain
   * the given object.
   *
   * Parameters:
   *
   * array - Array to check for the given obj.
   * obj - Object to find in the given array.
   */
  indexOf: function (array, obj) {
    if (array != null && obj != null) {
      for (var i = 0; i < array.length; i++) {
        if (array[i] == obj) {
          return i;
        }
      }
    }

    return -1;
  },

  /**
   * Function: forEach
   *
   * Calls the given function for each element of the given array and returns
   * the array.
   *
   * Parameters:
   *
   * array - Array that contains the elements.
   * fn - Function to be called for each object.
   */
  forEach: function (array, fn) {
    if (array != null && fn != null) {
      for (var i = 0; i < array.length; i++) {
        fn(array[i]);
      }
    }

    return array;
  },

  /**
   * Function: remove
   *
   * Removes all occurrences of the given object in the given array or
   * object. If there are multiple occurrences of the object, be they
   * associative or as an array entry, all occurrences are removed from
   * the array or deleted from the object. By removing the object from
   * the array, all elements following the removed element are shifted
   * by one step towards the beginning of the array.
   *
   * The length of arrays is not modified inside this function.
   *
   * Parameters:
   *
   * obj - Object to find in the given array.
   * array - Array to check for the given obj.
   */
  remove: function (obj, array) {
    var result = null;

    if (typeof array == 'object') {
      var index = mxUtils.indexOf(array, obj);

      while (index >= 0) {
        array.splice(index, 1);
        result = obj;
        index = mxUtils.indexOf(array, obj);
      }
    }

    for (var key in array) {
      if (array[key] == obj) {
        delete array[key];
        result = obj;
      }
    }

    return result;
  },

  /**
   * Function: isNode
   *
   * Returns true if the given value is an XML node with the node name
   * and if the optional attribute has the specified value.
   *
   * This implementation assumes that the given value is a DOM node if the
   * nodeType property is numeric, that is, if isNaN returns false for
   * value.nodeType.
   *
   * Parameters:
   *
   * value - Object that should be examined as a node.
   * nodeName - String that specifies the node name.
   * attributeName - Optional attribute name to check.
   * attributeValue - Optional attribute value to check.
   */
  isNode: function (value, nodeName, attributeName, attributeValue) {
    if (
      value != null &&
      !isNaN(value.nodeType) &&
      (nodeName == null || value.nodeName.toLowerCase() == nodeName.toLowerCase())
    ) {
      return attributeName == null || value.getAttribute(attributeName) == attributeValue;
    }

    return false;
  },

  /**
   * Function: isAncestorNode
   *
   * Returns true if the given ancestor is an ancestor of the
   * given DOM node in the DOM. This also returns true if the
   * child is the ancestor.
   *
   * Parameters:
   *
   * ancestor - DOM node that represents the ancestor.
   * child - DOM node that represents the child.
   */
  isAncestorNode: function (ancestor, child) {
    var parent = child;

    while (parent != null) {
      if (parent == ancestor) {
        return true;
      }

      parent = parent.parentNode;
    }

    return false;
  },

  /**
   * Function: getChildNodes
   *
   * Returns an array of child nodes that are of the given node type.
   *
   * Parameters:
   *
   * node - Parent DOM node to return the children from.
   * nodeType - Optional node type to return. Default is
   * <mxConstants.NODETYPE_ELEMENT>.
   */
  getChildNodes: function (node, nodeType) {
    nodeType = nodeType || mxConstants.NODETYPE_ELEMENT;

    var children = [];
    var tmp = node.firstChild;

    while (tmp != null) {
      if (tmp.nodeType == nodeType) {
        children.push(tmp);
      }

      tmp = tmp.nextSibling;
    }

    return children;
  },

  /**
   * Function: importNode
   *
   * Cross browser implementation for document.importNode. Uses document.importNode
   * in all browsers but IE, where the node is cloned by creating a new node and
   * copying all attributes and children into it using importNode, recursively.
   *
   * Parameters:
   *
   * doc - Document to import the node into.
   * node - Node to be imported.
   * allChildren - If all children should be imported.
   */
  importNode: function (doc, node, allChildren) {
    if (mxClient.IS_IE && (document.documentMode == null || document.documentMode < 10)) {
      return mxUtils.importNodeImplementation(doc, node, allChildren);
    } else {
      return doc.importNode(node, allChildren);
    }
  },

  /**
   * Function: importNodeImplementation
   *
   * Full DOM API implementation for importNode without using importNode API call.
   *
   * Parameters:
   *
   * doc - Document to import the node into.
   * node - Node to be imported.
   * allChildren - If all children should be imported.
   */
  importNodeImplementation: function (doc, node, allChildren) {
    switch (node.nodeType) {
      case 1 /* element */: {
        var newNode = doc.createElement(node.nodeName);

        if (node.attributes && node.attributes.length > 0) {
          for (var i = 0; i < node.attributes.length; i++) {
            newNode.setAttribute(node.attributes[i].nodeName, node.getAttribute(node.attributes[i].nodeName));
          }
        }

        if (allChildren && node.childNodes && node.childNodes.length > 0) {
          for (var i = 0; i < node.childNodes.length; i++) {
            newNode.appendChild(mxUtils.importNodeImplementation(doc, node.childNodes[i], allChildren));
          }
        }

        return newNode;
        break;
      }
      case 3: /* text */
      case 4: /* cdata-section */
      case 8 /* comment */: {
        return doc.createTextNode(node.nodeValue != null ? node.nodeValue : node.value);
        break;
      }
    }
  },

  /**
   * Function: createXmlDocument
   *
   * Returns a new, empty XML document.
   */
  createXmlDocument: function () {
    var doc = null;

    if (document.implementation && document.implementation.createDocument) {
      doc = document.implementation.createDocument('', '', null);
    } else if ('ActiveXObject' in window) {
      doc = mxUtils.createMsXmlDocument();
    }

    return doc;
  },

  /**
   * Function: createMsXmlDocument
   *
   * Returns a new, empty Microsoft.XMLDOM document using ActiveXObject.
   */
  createMsXmlDocument: function () {
    var doc = new ActiveXObject('Microsoft.XMLDOM');
    doc.async = false;

    // Workaround for parsing errors with SVG DTD
    doc.validateOnParse = false;
    doc.resolveExternals = false;

    return doc;
  },

  /**
   * Function: parseXml
   *
   * Parses the specified XML string into a new XML document and returns the
   * new document.
   *
   * Example:
   *
   * (code)
   * var doc = mxUtils.parseXml(
   *   '<mxGraphModel><root><MyDiagram id="0"><mxCell/></MyDiagram>'+
   *   '<MyLayer id="1"><mxCell parent="0" /></MyLayer><MyObject id="2">'+
   *   '<mxCell style="strokeColor=blue;fillColor=red" parent="1" vertex="1">'+
   *   '<mxGeometry x="10" y="10" width="80" height="30" as="geometry"/>'+
   *   '</mxCell></MyObject></root></mxGraphModel>');
   * (end)
   *
   * Parameters:
   *
   * xml - String that contains the XML data.
   */
  parseXml: (function () {
    if (window.DOMParser) {
      return function (xml) {
        var parser = new DOMParser();

        return parser.parseFromString(xml, 'text/xml');
      };
    } // IE<=9
    else {
      return function (xml) {
        var doc = mxUtils.createMsXmlDocument();
        doc.loadXML(xml);

        return doc;
      };
    }
  })(),

  /**
   * Function: clearSelection
   *
   * Clears the current selection in the page.
   */
  clearSelection: (function () {
    if (document.selection) {
      return function () {
        document.selection.empty();
      };
    } else if (window.getSelection) {
      return function () {
        if (window.getSelection().empty) {
          window.getSelection().empty();
        } else if (window.getSelection().removeAllRanges) {
          window.getSelection().removeAllRanges();
        }
      };
    } else {
      return function () {};
    }
  })(),

  /**
   * Function: removeWhitespace
   *
   * Removes the sibling text nodes for the given node that only consists
   * of tabs, newlines and spaces.
   *
   * Parameters:
   *
   * node - DOM node whose siblings should be removed.
   * before - Optional boolean that specifies the direction of the traversal.
   */
  removeWhitespace: function (node, before) {
    var tmp = before ? node.previousSibling : node.nextSibling;

    while (tmp != null && tmp.nodeType == mxConstants.NODETYPE_TEXT) {
      var next = before ? tmp.previousSibling : tmp.nextSibling;
      var text = mxUtils.getTextContent(tmp);

      if (mxUtils.trim(text).length == 0) {
        tmp.parentNode.removeChild(tmp);
      }

      tmp = next;
    }
  },

  /**
   * Function: htmlEntities
   *
   * Replaces characters (less than, greater than, newlines and quotes) with
   * their HTML entities in the given string and returns the result.
   *
   * Parameters:
   *
   * s - String that contains the characters to be converted.
   * newline - If newlines should be replaced. Default is true.
   */
  htmlEntities: function (s, newline) {
    s = String(s || '');

    s = s.replace(/&/g, '&amp;'); // 38 26
    s = s.replace(/"/g, '&quot;'); // 34 22
    s = s.replace(/\'/g, '&#39;'); // 39 27
    s = s.replace(/</g, '&lt;'); // 60 3C
    s = s.replace(/>/g, '&gt;'); // 62 3E

    if (newline == null || newline) {
      s = s.replace(/\n/g, '&#xa;');
    }

    return s;
  },

  /**
   * Function: isVml
   *
   * Returns true if the given node is in the VML namespace.
   *
   * Parameters:
   *
   * node - DOM node whose tag urn should be checked.
   */
  isVml: function (node) {
    return node != null && node.tagUrn == 'urn:schemas-microsoft-com:vml';
  },

  /**
   * Function: getXml
   *
   * Returns the XML content of the specified node. For Internet Explorer,
   * all \r\n\t[\t]* are removed from the XML string and the remaining \r\n
   * are replaced by \n. All \n are then replaced with linefeed, or &#xa; if
   * no linefeed is defined.
   *
   * Parameters:
   *
   * node - DOM node to return the XML for.
   * linefeed - Optional string that linefeeds are converted into. Default is
   * &#xa;
   */
  getXml: function (node, linefeed) {
    var xml = '';

    if (mxClient.IS_IE || mxClient.IS_IE11) {
      xml = mxUtils.getPrettyXml(node, '', '', '');
    } else if (window.XMLSerializer != null) {
      var xmlSerializer = new XMLSerializer();
      xml = xmlSerializer.serializeToString(node);
    } else if (node.xml != null) {
      xml = node.xml
        .replace(/\r\n\t[\t]*/g, '')
        .replace(/>\r\n/g, '>')
        .replace(/\r\n/g, '\n');
    }

    // Replaces linefeeds with HTML Entities.
    linefeed = linefeed || '&#xa;';
    xml = xml.replace(/\n/g, linefeed);

    return xml;
  },

  /**
   * Function: getPrettyXML
   *
   * Returns a pretty printed string that represents the XML tree for the
   * given node. This method should only be used to print XML for reading,
   * use <getXml> instead to obtain a string for processing.
   *
   * Parameters:
   *
   * node - DOM node to return the XML for.
   * tab - Optional string that specifies the indentation for one level.
   * Default is two spaces.
   * indent - Optional string that represents the current indentation.
   * Default is an empty string.
   * newline - Option string that represents a linefeed. Default is '\n'.
   */
  getPrettyXml: function (node, tab, indent, newline, ns) {
    var result = [];

    if (node != null) {
      tab = tab != null ? tab : '  ';
      indent = indent != null ? indent : '';
      newline = newline != null ? newline : '\n';

      if (node.namespaceURI != null && node.namespaceURI != ns) {
        ns = node.namespaceURI;

        if (node.getAttribute('xmlns') == null) {
          node.setAttribute('xmlns', node.namespaceURI);
        }
      }

      if (node.nodeType == mxConstants.NODETYPE_DOCUMENT) {
        result.push(mxUtils.getPrettyXml(node.documentElement, tab, indent, newline, ns));
      } else if (node.nodeType == mxConstants.NODETYPE_DOCUMENT_FRAGMENT) {
        var tmp = node.firstChild;

        if (tmp != null) {
          while (tmp != null) {
            result.push(mxUtils.getPrettyXml(tmp, tab, indent, newline, ns));
            tmp = tmp.nextSibling;
          }
        }
      } else if (node.nodeType == mxConstants.NODETYPE_COMMENT) {
        var value = mxUtils.getTextContent(node);

        if (value.length > 0) {
          result.push(indent + '<!--' + value + '-->' + newline);
        }
      } else if (node.nodeType == mxConstants.NODETYPE_TEXT) {
        var value = mxUtils.trim(mxUtils.getTextContent(node));

        if (value.length > 0) {
          result.push(indent + mxUtils.htmlEntities(value, false) + newline);
        }
      } else if (node.nodeType == mxConstants.NODETYPE_CDATA) {
        var value = mxUtils.getTextContent(node);

        if (value.length > 0) {
          result.push(indent + '<![CDATA[' + value + ']]' + newline);
        }
      } else {
        result.push(indent + '<' + node.nodeName);

        // Creates the string with the node attributes
        // and converts all HTML entities in the values
        var attrs = node.attributes;

        if (attrs != null) {
          for (var i = 0; i < attrs.length; i++) {
            var val = mxUtils.htmlEntities(attrs[i].value);
            result.push(' ' + attrs[i].nodeName + '="' + val + '"');
          }
        }

        // Recursively creates the XML string for each child
        // node and appends it here with an indentation
        var tmp = node.firstChild;

        if (tmp != null) {
          result.push('>' + newline);

          while (tmp != null) {
            result.push(mxUtils.getPrettyXml(tmp, tab, indent + tab, newline, ns));
            tmp = tmp.nextSibling;
          }

          result.push(indent + '</' + node.nodeName + '>' + newline);
        } else {
          result.push(' />' + newline);
        }
      }
    }

    return result.join('');
  },

  /**
   * Function: extractTextWithWhitespace
   *
   * Returns the text content of the specified node.
   *
   * Parameters:
   *
   * elems - DOM nodes to return the text for.
   */
  extractTextWithWhitespace: function (elems) {
    // Known block elements for handling linefeeds (list is not complete)
    var blocks = ['BLOCKQUOTE', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'OL', 'P', 'PRE', 'TABLE', 'UL'];
    var ret = [];

    function doExtract(elts) {
      // Single break should be ignored
      if (elts.length == 1 && (elts[0].nodeName == 'BR' || elts[0].innerHTML == '\n')) {
        return;
      }

      for (var i = 0; i < elts.length; i++) {
        var elem = elts[i];

        // DIV with a br or linefeed forces a linefeed
        if (
          elem.nodeName == 'BR' ||
          elem.innerHTML == '\n' ||
          ((elts.length == 1 || i == 0) && elem.nodeName == 'DIV' && elem.innerHTML.toLowerCase() == '<br>')
        ) {
          ret.push('\n');
        } else {
          if (elem.nodeType === 3 || elem.nodeType === 4) {
            if (elem.nodeValue.length > 0) {
              ret.push(elem.nodeValue);
            }
          } else if (elem.nodeType !== 8 && elem.childNodes.length > 0) {
            doExtract(elem.childNodes);
          }

          if (i < elts.length - 1 && mxUtils.indexOf(blocks, elts[i + 1].nodeName) >= 0) {
            ret.push('\n');
          }
        }
      }
    }

    doExtract(elems);

    return ret.join('');
  },

  /**
   * Function: replaceTrailingNewlines
   *
   * Replaces each trailing newline with the given pattern.
   */
  replaceTrailingNewlines: function (str, pattern) {
    // LATER: Check is this can be done with a regular expression
    var postfix = '';

    while (str.length > 0 && str.charAt(str.length - 1) == '\n') {
      str = str.substring(0, str.length - 1);
      postfix += pattern;
    }

    return str + postfix;
  },

  /**
   * Function: getTextContent
   *
   * Returns the text content of the specified node.
   *
   * Parameters:
   *
   * node - DOM node to return the text content for.
   */
  getTextContent: function (node) {
    // Only IE10-
    if (mxClient.IS_IE && node.innerText !== undefined) {
      return node.innerText;
    } else {
      return node != null ? node[node.textContent === undefined ? 'text' : 'textContent'] : '';
    }
  },

  /**
   * Function: setTextContent
   *
   * Sets the text content of the specified node.
   *
   * Parameters:
   *
   * node - DOM node to set the text content for.
   * text - String that represents the text content.
   */
  setTextContent: function (node, text) {
    if (node.innerText !== undefined) {
      node.innerText = text;
    } else {
      node[node.textContent === undefined ? 'text' : 'textContent'] = text;
    }
  },

  /**
   * Function: getInnerHtml
   *
   * Returns the inner HTML for the given node as a string or an empty string
   * if no node was specified. The inner HTML is the text representing all
   * children of the node, but not the node itself.
   *
   * Parameters:
   *
   * node - DOM node to return the inner HTML for.
   */
  getInnerHtml: (function () {
    if (mxClient.IS_IE) {
      return function (node) {
        if (node != null) {
          return node.innerHTML;
        }

        return '';
      };
    } else {
      return function (node) {
        if (node != null) {
          var serializer = new XMLSerializer();
          return serializer.serializeToString(node);
        }

        return '';
      };
    }
  })(),

  /**
   * Function: getOuterHtml
   *
   * Returns the outer HTML for the given node as a string or an empty
   * string if no node was specified. The outer HTML is the text representing
   * all children of the node including the node itself.
   *
   * Parameters:
   *
   * node - DOM node to return the outer HTML for.
   */
  getOuterHtml: (function () {
    if (mxClient.IS_IE) {
      return function (node) {
        if (node != null) {
          if (node.outerHTML != null) {
            return node.outerHTML;
          } else {
            var tmp = [];
            tmp.push('<' + node.nodeName);

            var attrs = node.attributes;

            if (attrs != null) {
              for (var i = 0; i < attrs.length; i++) {
                var value = attrs[i].value;

                if (value != null && value.length > 0) {
                  tmp.push(' ');
                  tmp.push(attrs[i].nodeName);
                  tmp.push('="');
                  tmp.push(value);
                  tmp.push('"');
                }
              }
            }

            if (node.innerHTML.length == 0) {
              tmp.push('/>');
            } else {
              tmp.push('>');
              tmp.push(node.innerHTML);
              tmp.push('</' + node.nodeName + '>');
            }

            return tmp.join('');
          }
        }

        return '';
      };
    } else {
      return function (node) {
        if (node != null) {
          var serializer = new XMLSerializer();
          return serializer.serializeToString(node);
        }

        return '';
      };
    }
  })(),

  /**
   * Function: write
   *
   * Creates a text node for the given string and appends it to the given
   * parent. Returns the text node.
   *
   * Parameters:
   *
   * parent - DOM node to append the text node to.
   * text - String representing the text to be added.
   */
  write: function (parent, text) {
    var doc = parent.ownerDocument;
    var node = doc.createTextNode(text);

    if (parent != null) {
      parent.appendChild(node);
    }

    return node;
  },

  /**
   * Function: writeln
   *
   * Creates a text node for the given string and appends it to the given
   * parent with an additional linefeed. Returns the text node.
   *
   * Parameters:
   *
   * parent - DOM node to append the text node to.
   * text - String representing the text to be added.
   */
  writeln: function (parent, text) {
    var doc = parent.ownerDocument;
    var node = doc.createTextNode(text);

    if (parent != null) {
      parent.appendChild(node);
      parent.appendChild(document.createElement('br'));
    }

    return node;
  },

  /**
   * Function: br
   *
   * Appends a linebreak to the given parent and returns the linebreak.
   *
   * Parameters:
   *
   * parent - DOM node to append the linebreak to.
   */
  br: function (parent, count) {
    count = count || 1;
    var br = null;

    for (var i = 0; i < count; i++) {
      if (parent != null) {
        br = parent.ownerDocument.createElement('br');
        parent.appendChild(br);
      }
    }

    return br;
  },

  /**
   * Function: button
   *
   * Returns a new button with the given level and function as an onclick
   * event handler.
   *
   * (code)
   * document.body.appendChild(mxUtils.button('Test', function(evt)
   * {
   *   alert('Hello, World!');
   * }));
   * (end)
   *
   * Parameters:
   *
   * label - String that represents the label of the button.
   * funct - Function to be called if the button is pressed.
   * doc - Optional document to be used for creating the button. Default is the
   * current document.
   */
  button: function (label, funct, doc) {
    doc = doc != null ? doc : document;

    var button = doc.createElement('button');
    mxUtils.write(button, label);

    mxEvent.addListener(button, 'click', function (evt) {
      funct(evt);
    });

    return button;
  },

  /**
   * Function: para
   *
   * Appends a new paragraph with the given text to the specified parent and
   * returns the paragraph.
   *
   * Parameters:
   *
   * parent - DOM node to append the text node to.
   * text - String representing the text for the new paragraph.
   */
  para: function (parent, text) {
    var p = document.createElement('p');
    mxUtils.write(p, text);

    if (parent != null) {
      parent.appendChild(p);
    }

    return p;
  },

  /**
   * Function: addTransparentBackgroundFilter
   *
   * Adds a transparent background to the filter of the given node. This
   * background can be used in IE8 standards mode (native IE8 only) to pass
   * events through the node.
   */
  addTransparentBackgroundFilter: function (node) {
    node.style.filter +=
      "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
      mxClient.imageBasePath +
      "/transparent.gif', sizingMethod='scale')";
  },

  /**
   * Function: linkAction
   *
   * Adds a hyperlink to the specified parent that invokes action on the
   * specified editor.
   *
   * Parameters:
   *
   * parent - DOM node to contain the new link.
   * text - String that is used as the link label.
   * editor - <mxEditor> that will execute the action.
   * action - String that defines the name of the action to be executed.
   * pad - Optional left-padding for the link. Default is 0.
   */
  linkAction: function (parent, text, editor, action, pad) {
    return mxUtils.link(
      parent,
      text,
      function () {
        editor.execute(action);
      },
      pad
    );
  },

  /**
   * Function: linkInvoke
   *
   * Adds a hyperlink to the specified parent that invokes the specified
   * function on the editor passing along the specified argument. The
   * function name is the name of a function of the editor instance,
   * not an action name.
   *
   * Parameters:
   *
   * parent - DOM node to contain the new link.
   * text - String that is used as the link label.
   * editor - <mxEditor> instance to execute the function on.
   * functName - String that represents the name of the function.
   * arg - Object that represents the argument to the function.
   * pad - Optional left-padding for the link. Default is 0.
   */
  linkInvoke: function (parent, text, editor, functName, arg, pad) {
    return mxUtils.link(
      parent,
      text,
      function () {
        editor[functName](arg);
      },
      pad
    );
  },

  /**
   * Function: link
   *
   * Adds a hyperlink to the specified parent and invokes the given function
   * when the link is clicked.
   *
   * Parameters:
   *
   * parent - DOM node to contain the new link.
   * text - String that is used as the link label.
   * funct - Function to execute when the link is clicked.
   * pad - Optional left-padding for the link. Default is 0.
   */
  link: function (parent, text, funct, pad) {
    var a = document.createElement('span');

    a.style.color = 'blue';
    a.style.textDecoration = 'underline';
    a.style.cursor = 'pointer';

    if (pad != null) {
      a.style.paddingLeft = pad + 'px';
    }

    mxEvent.addListener(a, 'click', funct);
    mxUtils.write(a, text);

    if (parent != null) {
      parent.appendChild(a);
    }

    return a;
  },

  /**
   * Function: getDocumentSize
   *
   * Returns the client size for the current document as an <mxRectangle>.
   */
  getDocumentSize: function () {
    var b = document.body;
    var d = document.documentElement;

    try {
      return new mxRectangle(0, 0, b.clientWidth || d.clientWidth, Math.max(b.clientHeight || 0, d.clientHeight));
    } catch (e) {
      return new mxRectangle();
    }
  },

  /**
   * Function: fit
   *
   * Makes sure the given node is inside the visible area of the window. This
   * is done by setting the left and top in the style.
   */
  fit: function (node) {
    var ds = mxUtils.getDocumentSize();
    var left = parseInt(node.offsetLeft);
    var width = parseInt(node.offsetWidth);

    var offset = mxUtils.getDocumentScrollOrigin(node.ownerDocument);
    var sl = offset.x;
    var st = offset.y;

    var b = document.body;
    var d = document.documentElement;
    var right = sl + ds.width;

    if (left + width > right) {
      node.style.left = Math.max(sl, right - width) + 'px';
    }

    var top = parseInt(node.offsetTop);
    var height = parseInt(node.offsetHeight);

    var bottom = st + ds.height;

    if (top + height > bottom) {
      node.style.top = Math.max(st, bottom - height) + 'px';
    }
  },

  /**
   * Function: load
   *
   * Loads the specified URL *synchronously* and returns the <mxXmlRequest>.
   * Throws an exception if the file cannot be loaded. See <mxUtils.get> for
   * an asynchronous implementation.
   *
   * Example:
   *
   * (code)
   * try
   * {
   *   var req = mxUtils.load(filename);
   *   var root = req.getDocumentElement();
   *   // Process XML DOM...
   * }
   * catch (ex)
   * {
   *   mxUtils.alert('Cannot load '+filename+': '+ex);
   * }
   * (end)
   *
   * Parameters:
   *
   * url - URL to get the data from.
   */
  load: function (url) {
    var req = new mxXmlRequest(url, null, 'GET', false);
    req.send();

    return req;
  },

  /**
   * Function: get
   *
   * Loads the specified URL *asynchronously* and invokes the given functions
   * depending on the request status. Returns the <mxXmlRequest> in use. Both
   * functions take the <mxXmlRequest> as the only parameter. See
   * <mxUtils.load> for a synchronous implementation.
   *
   * Example:
   *
   * (code)
   * mxUtils.get(url, function(req)
   * {
   *    var node = req.getDocumentElement();
   *    // Process XML DOM...
   * });
   * (end)
   *
   * So for example, to load a diagram into an existing graph model, the
   * following code is used.
   *
   * (code)
   * mxUtils.get(url, function(req)
   * {
   *   var node = req.getDocumentElement();
   *   var dec = new mxCodec(node.ownerDocument);
   *   dec.decode(node, graph.getModel());
   * });
   * (end)
   *
   * Parameters:
   *
   * url - URL to get the data from.
   * onload - Optional function to execute for a successful response.
   * onerror - Optional function to execute on error.
   * binary - Optional boolean parameter that specifies if the request is
   * binary.
   * timeout - Optional timeout in ms before calling ontimeout.
   * ontimeout - Optional function to execute on timeout.
   * headers - Optional with headers, eg. {'Authorization': 'token xyz'}
   */
  get: function (url, onload, onerror, binary, timeout, ontimeout, headers) {
    var req = new mxXmlRequest(url, null, 'GET');
    var setRequestHeaders = req.setRequestHeaders;

    if (headers) {
      req.setRequestHeaders = function (request, params) {
        setRequestHeaders.apply(this, arguments);

        for (var key in headers) {
          request.setRequestHeader(key, headers[key]);
        }
      };
    }

    if (binary != null) {
      req.setBinary(binary);
    }

    req.send(onload, onerror, timeout, ontimeout);

    return req;
  },

  /**
   * Function: getAll
   *
   * Loads the URLs in the given array *asynchronously* and invokes the given function
   * if all requests returned with a valid 2xx status. The error handler is invoked
   * once on the first error or invalid response.
   *
   * Parameters:
   *
   * urls - Array of URLs to be loaded.
   * onload - Callback with array of <mxXmlRequests>.
   * onerror - Optional function to execute on error.
   */
  getAll: function (urls, onload, onerror) {
    var remain = urls.length;
    var result = [];
    var errors = 0;
    var err = function () {
      if (errors == 0 && onerror != null) {
        onerror();
      }

      errors++;
    };

    for (var i = 0; i < urls.length; i++) {
      (function (url, index) {
        mxUtils.get(
          url,
          function (req) {
            var status = req.getStatus();

            if (status < 200 || status > 299) {
              err();
            } else {
              result[index] = req;
              remain--;

              if (remain == 0) {
                onload(result);
              }
            }
          },
          err
        );
      })(urls[i], i);
    }

    if (remain == 0) {
      onload(result);
    }
  },

  /**
   * Function: post
   *
   * Posts the specified params to the given URL *asynchronously* and invokes
   * the given functions depending on the request status. Returns the
   * <mxXmlRequest> in use. Both functions take the <mxXmlRequest> as the
   * only parameter. Make sure to use encodeURIComponent for the parameter
   * values.
   *
   * Example:
   *
   * (code)
   * mxUtils.post(url, 'key=value', function(req)
   * {
   * 	mxUtils.alert('Ready: '+req.isReady()+' Status: '+req.getStatus());
   *  // Process req.getDocumentElement() using DOM API if OK...
   * });
   * (end)
   *
   * Parameters:
   *
   * url - URL to get the data from.
   * params - Parameters for the post request.
   * onload - Optional function to execute for a successful response.
   * onerror - Optional function to execute on error.
   */
  post: function (url, params, onload, onerror) {
    return new mxXmlRequest(url, params).send(onload, onerror);
  },

  /**
   * Function: submit
   *
   * Submits the given parameters to the specified URL using
   * <mxXmlRequest.simulate> and returns the <mxXmlRequest>.
   * Make sure to use encodeURIComponent for the parameter
   * values.
   *
   * Parameters:
   *
   * url - URL to get the data from.
   * params - Parameters for the form.
   * doc - Document to create the form in.
   * target - Target to send the form result to.
   */
  submit: function (url, params, doc, target) {
    return new mxXmlRequest(url, params).simulate(doc, target);
  },

  /**
   * Function: loadInto
   *
   * Loads the specified URL *asynchronously* into the specified document,
   * invoking onload after the document has been loaded. This implementation
   * does not use <mxXmlRequest>, but the document.load method.
   *
   * Parameters:
   *
   * url - URL to get the data from.
   * doc - The document to load the URL into.
   * onload - Function to execute when the URL has been loaded.
   */
  loadInto: function (url, doc, onload) {
    if (mxClient.IS_IE) {
      doc.onreadystatechange = function () {
        if (doc.readyState == 4) {
          onload();
        }
      };
    } else {
      doc.addEventListener('load', onload, false);
    }

    doc.load(url);
  },

  /**
   * Function: getValue
   *
   * Returns the value for the given key in the given associative array or
   * the given default value if the value is null.
   *
   * Parameters:
   *
   * array - Associative array that contains the value for the key.
   * key - Key whose value should be returned.
   * defaultValue - Value to be returned if the value for the given
   * key is null.
   */
  getValue: function (array, key, defaultValue) {
    var value = array != null ? array[key] : null;

    if (value == null) {
      value = defaultValue;
    }

    return value;
  },

  /**
   * Function: getNumber
   *
   * Returns the numeric value for the given key in the given associative
   * array or the given default value (or 0) if the value is null. The value
   * is converted to a numeric value using the Number function.
   *
   * Parameters:
   *
   * array - Associative array that contains the value for the key.
   * key - Key whose value should be returned.
   * defaultValue - Value to be returned if the value for the given
   * key is null. Default is 0.
   */
  getNumber: function (array, key, defaultValue) {
    var value = array != null ? array[key] : null;

    if (value == null) {
      value = defaultValue || 0;
    }

    return Number(value);
  },

  /**
   * Function: getColor
   *
   * Returns the color value for the given key in the given associative
   * array or the given default value if the value is null. If the value
   * is <mxConstants.NONE> then null is returned.
   *
   * Parameters:
   *
   * array - Associative array that contains the value for the key.
   * key - Key whose value should be returned.
   * defaultValue - Value to be returned if the value for the given
   * key is null. Default is null.
   */
  getColor: function (array, key, defaultValue) {
    var value = array != null ? array[key] : null;

    if (value == null) {
      value = defaultValue;
    } else if (value == mxConstants.NONE) {
      value = null;
    }

    return value;
  },

  /**
   * Function: clone
   *
   * Recursively clones the specified object ignoring all fieldnames in the
   * given array of transient fields. <mxObjectIdentity.FIELD_NAME> is always
   * ignored by this function.
   *
   * Parameters:
   *
   * obj - Object to be cloned.
   * transients - Optional array of strings representing the fieldname to be
   * ignored.
   * shallow - Optional boolean argument to specify if a shallow clone should
   * be created, that is, one where all object references are not cloned or,
   * in other words, one where only atomic (strings, numbers) values are
   * cloned. Default is false.
   */
  clone: function (obj, transients, shallow) {
    shallow = shallow != null ? shallow : false;
    var clone = null;

    if (obj != null && typeof obj.constructor == 'function') {
      clone = new obj.constructor();

      for (var i in obj) {
        if (i != mxObjectIdentity.FIELD_NAME && (transients == null || mxUtils.indexOf(transients, i) < 0)) {
          if (!shallow && typeof obj[i] == 'object') {
            clone[i] = mxUtils.clone(obj[i]);
          } else {
            clone[i] = obj[i];
          }
        }
      }
    }

    return clone;
  },

  /**
   * Function: equalPoints
   *
   * Compares all mxPoints in the given lists.
   *
   * Parameters:
   *
   * a - Array of <mxPoints> to be compared.
   * b - Array of <mxPoints> to be compared.
   */
  equalPoints: function (a, b) {
    if ((a == null && b != null) || (a != null && b == null) || (a != null && b != null && a.length != b.length)) {
      return false;
    } else if (a != null && b != null) {
      for (var i = 0; i < a.length; i++) {
        if (
          (a[i] != null && b[i] == null) ||
          (a[i] == null && b[i] != null) ||
          (a[i] != null && b[i] != null && (a[i].x != b[i].x || a[i].y != b[i].y))
        ) {
          return false;
        }
      }
    }

    return true;
  },

  /**
   * Function: equalEntries
   *
   * Returns true if all properties of the given objects are equal. Values
   * with NaN are equal to NaN and unequal to any other value.
   *
   * Parameters:
   *
   * a - First object to be compared.
   * b - Second object to be compared.
   */
  equalEntries: function (a, b) {
    // Counts keys in b to check if all values have been compared
    var count = 0;

    if ((a == null && b != null) || (a != null && b == null) || (a != null && b != null && a.length != b.length)) {
      return false;
    } else if (a != null && b != null) {
      for (var key in b) {
        count++;
      }

      for (var key in a) {
        count--;

        if ((!mxUtils.isNaN(a[key]) || !mxUtils.isNaN(b[key])) && a[key] != b[key]) {
          return false;
        }
      }
    }

    return count == 0;
  },

  /**
   * Function: removeDuplicates
   *
   * Removes all duplicates from the given array.
   */
  removeDuplicates: function (arr) {
    var dict = new mxDictionary();
    var result = [];

    for (var i = 0; i < arr.length; i++) {
      if (!dict.get(arr[i])) {
        result.push(arr[i]);
        dict.put(arr[i], true);
      }
    }

    return result;
  },

  /**
   * Function: isNaN
   *
   * Returns true if the given value is of type number and isNaN returns true.
   */
  isNaN: function (value) {
    return typeof value == 'number' && isNaN(value);
  },

  /**
   * Function: extend
   *
   * Assigns a copy of the superclass prototype to the subclass prototype.
   * Note that this does not call the constructor of the superclass at this
   * point, the superclass constructor should be called explicitely in the
   * subclass constructor. Below is an example.
   *
   * (code)
   * MyGraph = function(container, model, renderHint, stylesheet)
   * {
   *   mxGraph.call(this, container, model, renderHint, stylesheet);
   * }
   *
   * mxUtils.extend(MyGraph, mxGraph);
   * (end)
   *
   * Parameters:
   *
   * ctor - Constructor of the subclass.
   * superCtor - Constructor of the superclass.
   */
  extend: function (ctor, superCtor) {
    var f = function () {};
    f.prototype = superCtor.prototype;

    ctor.prototype = new f();
    ctor.prototype.constructor = ctor;
  },

  /**
   * Function: toString
   *
   * Returns a textual representation of the specified object.
   *
   * Parameters:
   *
   * obj - Object to return the string representation for.
   */
  toString: function (obj) {
    var output = '';

    for (var i in obj) {
      try {
        if (obj[i] == null) {
          output += i + ' = [null]\n';
        } else if (typeof obj[i] == 'function') {
          output += i + ' => [Function]\n';
        } else if (typeof obj[i] == 'object') {
          var ctor = mxUtils.getFunctionName(obj[i].constructor);
          output += i + ' => [' + ctor + ']\n';
        } else {
          output += i + ' = ' + obj[i] + '\n';
        }
      } catch (e) {
        output += i + '=' + e.message;
      }
    }

    return output;
  },

  /**
   * Function: toRadians
   *
   * Converts the given degree to radians.
   */
  toRadians: function (deg) {
    return (Math.PI * deg) / 180;
  },

  /**
   * Function: toDegree
   *
   * Converts the given radians to degree.
   */
  toDegree: function (rad) {
    return (rad * 180) / Math.PI;
  },

  /**
   * Function: arcToCurves
   *
   * Converts the given arc to a series of curves.
   */
  arcToCurves: function (x0, y0, r1, r2, angle, largeArcFlag, sweepFlag, x, y) {
    x -= x0;
    y -= y0;

    if (r1 === 0 || r2 === 0) {
      return result;
    }

    var fS = sweepFlag;
    var psai = angle;
    r1 = Math.abs(r1);
    r2 = Math.abs(r2);
    var ctx = -x / 2;
    var cty = -y / 2;
    var cpsi = Math.cos((psai * Math.PI) / 180);
    var spsi = Math.sin((psai * Math.PI) / 180);
    var rxd = cpsi * ctx + spsi * cty;
    var ryd = -1 * spsi * ctx + cpsi * cty;
    var rxdd = rxd * rxd;
    var rydd = ryd * ryd;
    var r1x = r1 * r1;
    var r2y = r2 * r2;
    var lamda = rxdd / r1x + rydd / r2y;
    var sds;

    if (lamda > 1) {
      r1 = Math.sqrt(lamda) * r1;
      r2 = Math.sqrt(lamda) * r2;
      sds = 0;
    } else {
      var seif = 1;

      if (largeArcFlag === fS) {
        seif = -1;
      }

      sds = seif * Math.sqrt((r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd));
    }

    var txd = (sds * r1 * ryd) / r2;
    var tyd = (-1 * sds * r2 * rxd) / r1;
    var tx = cpsi * txd - spsi * tyd + x / 2;
    var ty = spsi * txd + cpsi * tyd + y / 2;
    var rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1);
    var s1 = rad >= 0 ? rad : 2 * Math.PI + rad;
    rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1);
    var dr = rad >= 0 ? rad : 2 * Math.PI + rad;

    if (fS == 0 && dr > 0) {
      dr -= 2 * Math.PI;
    } else if (fS != 0 && dr < 0) {
      dr += 2 * Math.PI;
    }

    var sse = (dr * 2) / Math.PI;
    var seg = Math.ceil(sse < 0 ? -1 * sse : sse);
    var segr = dr / seg;
    var t = ((8 / 3) * Math.sin(segr / 4) * Math.sin(segr / 4)) / Math.sin(segr / 2);
    var cpsir1 = cpsi * r1;
    var cpsir2 = cpsi * r2;
    var spsir1 = spsi * r1;
    var spsir2 = spsi * r2;
    var mc = Math.cos(s1);
    var ms = Math.sin(s1);
    var x2 = -t * (cpsir1 * ms + spsir2 * mc);
    var y2 = -t * (spsir1 * ms - cpsir2 * mc);
    var x3 = 0;
    var y3 = 0;

    var result = [];

    for (var n = 0; n < seg; ++n) {
      s1 += segr;
      mc = Math.cos(s1);
      ms = Math.sin(s1);

      x3 = cpsir1 * mc - spsir2 * ms + tx;
      y3 = spsir1 * mc + cpsir2 * ms + ty;
      var dx = -t * (cpsir1 * ms + spsir2 * mc);
      var dy = -t * (spsir1 * ms - cpsir2 * mc);

      // CurveTo updates x0, y0 so need to restore it
      var index = n * 6;
      result[index] = Number(x2 + x0);
      result[index + 1] = Number(y2 + y0);
      result[index + 2] = Number(x3 - dx + x0);
      result[index + 3] = Number(y3 - dy + y0);
      result[index + 4] = Number(x3 + x0);
      result[index + 5] = Number(y3 + y0);

      x2 = x3 + dx;
      y2 = y3 + dy;
    }

    return result;
  },

  /**
   * Function: getBoundingBox
   *
   * Returns the bounding box for the rotated rectangle.
   *
   * Parameters:
   *
   * rect - <mxRectangle> to be rotated.
   * angle - Number that represents the angle (in degrees).
   * cx - Optional <mxPoint> that represents the rotation center. If no
   * rotation center is given then the center of rect is used.
   */
  getBoundingBox: function (rect, rotation, cx) {
    var result = null;

    if (rect != null && rotation != null && rotation != 0) {
      var rad = mxUtils.toRadians(rotation);
      var cos = Math.cos(rad);
      var sin = Math.sin(rad);

      cx = cx != null ? cx : new mxPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);

      var p1 = new mxPoint(rect.x, rect.y);
      var p2 = new mxPoint(rect.x + rect.width, rect.y);
      var p3 = new mxPoint(p2.x, rect.y + rect.height);
      var p4 = new mxPoint(rect.x, p3.y);

      p1 = mxUtils.getRotatedPoint(p1, cos, sin, cx);
      p2 = mxUtils.getRotatedPoint(p2, cos, sin, cx);
      p3 = mxUtils.getRotatedPoint(p3, cos, sin, cx);
      p4 = mxUtils.getRotatedPoint(p4, cos, sin, cx);

      result = new mxRectangle(p1.x, p1.y, 0, 0);
      result.add(new mxRectangle(p2.x, p2.y, 0, 0));
      result.add(new mxRectangle(p3.x, p3.y, 0, 0));
      result.add(new mxRectangle(p4.x, p4.y, 0, 0));
    }

    return result;
  },

  /**
   * Function: getRotatedPoint
   *
   * Rotates the given point by the given cos and sin.
   */
  getRotatedPoint: function (pt, cos, sin, c) {
    c = c != null ? c : new mxPoint();
    var x = pt.x - c.x;
    var y = pt.y - c.y;

    var x1 = x * cos - y * sin;
    var y1 = y * cos + x * sin;

    return new mxPoint(x1 + c.x, y1 + c.y);
  },

  /**
   * Returns an integer mask of the port constraints of the given map
   * @param dict the style map to determine the port constraints for
   * @param defaultValue Default value to return if the key is undefined.
   * @return the mask of port constraint directions
   *
   * Parameters:
   *
   * terminal - <mxCelState> that represents the terminal.
   * edge - <mxCellState> that represents the edge.
   * source - Boolean that specifies if the terminal is the source terminal.
   * defaultValue - Default value to be returned.
   */
  getPortConstraints: function (terminal, edge, source, defaultValue) {
    var value = mxUtils.getValue(
      terminal.style,
      mxConstants.STYLE_PORT_CONSTRAINT,
      mxUtils.getValue(
        edge.style,
        source ? mxConstants.STYLE_SOURCE_PORT_CONSTRAINT : mxConstants.STYLE_TARGET_PORT_CONSTRAINT,
        null
      )
    );

    if (value == null) {
      return defaultValue;
    } else {
      var directions = value.toString();
      var returnValue = mxConstants.DIRECTION_MASK_NONE;
      var constraintRotationEnabled = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT_ROTATION, 0);
      var rotation = 0;

      if (constraintRotationEnabled == 1) {
        rotation = mxUtils.getValue(terminal.style, mxConstants.STYLE_ROTATION, 0);
      }

      var quad = 0;

      if (rotation > 45) {
        quad = 1;

        if (rotation >= 135) {
          quad = 2;
        }
      } else if (rotation < -45) {
        quad = 3;

        if (rotation <= -135) {
          quad = 2;
        }
      }

      if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0) {
        switch (quad) {
          case 0:
            returnValue |= mxConstants.DIRECTION_MASK_NORTH;
            break;
          case 1:
            returnValue |= mxConstants.DIRECTION_MASK_EAST;
            break;
          case 2:
            returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
            break;
          case 3:
            returnValue |= mxConstants.DIRECTION_MASK_WEST;
            break;
        }
      }
      if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0) {
        switch (quad) {
          case 0:
            returnValue |= mxConstants.DIRECTION_MASK_WEST;
            break;
          case 1:
            returnValue |= mxConstants.DIRECTION_MASK_NORTH;
            break;
          case 2:
            returnValue |= mxConstants.DIRECTION_MASK_EAST;
            break;
          case 3:
            returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
            break;
        }
      }
      if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0) {
        switch (quad) {
          case 0:
            returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
            break;
          case 1:
            returnValue |= mxConstants.DIRECTION_MASK_WEST;
            break;
          case 2:
            returnValue |= mxConstants.DIRECTION_MASK_NORTH;
            break;
          case 3:
            returnValue |= mxConstants.DIRECTION_MASK_EAST;
            break;
        }
      }
      if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0) {
        switch (quad) {
          case 0:
            returnValue |= mxConstants.DIRECTION_MASK_EAST;
            break;
          case 1:
            returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
            break;
          case 2:
            returnValue |= mxConstants.DIRECTION_MASK_WEST;
            break;
          case 3:
            returnValue |= mxConstants.DIRECTION_MASK_NORTH;
            break;
        }
      }

      return returnValue;
    }
  },

  /**
   * Function: reversePortConstraints
   *
   * Reverse the port constraint bitmask. For example, north | east
   * becomes south | west
   */
  reversePortConstraints: function (constraint) {
    var result = 0;

    result = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3;
    result |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1;
    result |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1;
    result |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3;

    return result;
  },

  /**
   * Function: findNearestSegment
   *
   * Finds the index of the nearest segment on the given cell state for
   * the specified coordinate pair.
   */
  findNearestSegment: function (state, x, y) {
    var index = -1;

    if (state.absolutePoints.length > 0) {
      var last = state.absolutePoints[0];
      var min = null;

      for (var i = 1; i < state.absolutePoints.length; i++) {
        var current = state.absolutePoints[i];
        var dist = mxUtils.ptSegDistSq(last.x, last.y, current.x, current.y, x, y);

        if (min == null || dist < min) {
          min = dist;
          index = i - 1;
        }

        last = current;
      }
    }

    return index;
  },

  /**
   * Function: getDirectedBounds
   *
   * Adds the given margins to the given rectangle and rotates and flips the
   * rectangle according to the respective styles in style.
   */
  getDirectedBounds: function (rect, m, style, flipH, flipV) {
    var d = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
    flipH = flipH != null ? flipH : mxUtils.getValue(style, mxConstants.STYLE_FLIPH, false);
    flipV = flipV != null ? flipV : mxUtils.getValue(style, mxConstants.STYLE_FLIPV, false);

    m.x = Math.round(Math.max(0, Math.min(rect.width, m.x)));
    m.y = Math.round(Math.max(0, Math.min(rect.height, m.y)));
    m.width = Math.round(Math.max(0, Math.min(rect.width, m.width)));
    m.height = Math.round(Math.max(0, Math.min(rect.height, m.height)));

    if (
      (flipV && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||
      (flipH && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST))
    ) {
      var tmp = m.x;
      m.x = m.width;
      m.width = tmp;
    }

    if (
      (flipH && (d == mxConstants.DIRECTION_SOUTH || d == mxConstants.DIRECTION_NORTH)) ||
      (flipV && (d == mxConstants.DIRECTION_EAST || d == mxConstants.DIRECTION_WEST))
    ) {
      var tmp = m.y;
      m.y = m.height;
      m.height = tmp;
    }

    var m2 = mxRectangle.fromRectangle(m);

    if (d == mxConstants.DIRECTION_SOUTH) {
      m2.y = m.x;
      m2.x = m.height;
      m2.width = m.y;
      m2.height = m.width;
    } else if (d == mxConstants.DIRECTION_WEST) {
      m2.y = m.height;
      m2.x = m.width;
      m2.width = m.x;
      m2.height = m.y;
    } else if (d == mxConstants.DIRECTION_NORTH) {
      m2.y = m.width;
      m2.x = m.y;
      m2.width = m.height;
      m2.height = m.x;
    }

    return new mxRectangle(rect.x + m2.x, rect.y + m2.y, rect.width - m2.width - m2.x, rect.height - m2.height - m2.y);
  },

  /**
   * Function: getPerimeterPoint
   *
   * Returns the intersection between the polygon defined by the array of
   * points and the line between center and point.
   */
  getPerimeterPoint: function (pts, center, point) {
    var min = null;

    for (var i = 0; i < pts.length - 1; i++) {
      var pt = mxUtils.intersection(
        pts[i].x,
        pts[i].y,
        pts[i + 1].x,
        pts[i + 1].y,
        center.x,
        center.y,
        point.x,
        point.y
      );

      if (pt != null) {
        var dx = point.x - pt.x;
        var dy = point.y - pt.y;
        var ip = { p: pt, distSq: dy * dy + dx * dx };

        if (ip != null && (min == null || min.distSq > ip.distSq)) {
          min = ip;
        }
      }
    }

    return min != null ? min.p : null;
  },

  /**
   * Function: rectangleIntersectsSegment
   *
   * Returns true if the given rectangle intersects the given segment.
   *
   * Parameters:
   *
   * bounds - <mxRectangle> that represents the rectangle.
   * p1 - <mxPoint> that represents the first point of the segment.
   * p2 - <mxPoint> that represents the second point of the segment.
   */
  rectangleIntersectsSegment: function (bounds, p1, p2) {
    var top = bounds.y;
    var left = bounds.x;
    var bottom = top + bounds.height;
    var right = left + bounds.width;

    // Find min and max X for the segment
    var minX = p1.x;
    var maxX = p2.x;

    if (p1.x > p2.x) {
      minX = p2.x;
      maxX = p1.x;
    }

    // Find the intersection of the segment's and rectangle's x-projections
    if (maxX > right) {
      maxX = right;
    }

    if (minX < left) {
      minX = left;
    }

    if (minX > maxX) {
      // If their projections do not intersect return false
      return false;
    }

    // Find corresponding min and max Y for min and max X we found before
    var minY = p1.y;
    var maxY = p2.y;
    var dx = p2.x - p1.x;

    if (Math.abs(dx) > 0.0000001) {
      var a = (p2.y - p1.y) / dx;
      var b = p1.y - a * p1.x;
      minY = a * minX + b;
      maxY = a * maxX + b;
    }

    if (minY > maxY) {
      var tmp = maxY;
      maxY = minY;
      minY = tmp;
    }

    // Find the intersection of the segment's and rectangle's y-projections
    if (maxY > bottom) {
      maxY = bottom;
    }

    if (minY < top) {
      minY = top;
    }

    if (minY > maxY) {
      // If Y-projections do not intersect return false
      return false;
    }

    return true;
  },

  /**
   * Function: contains
   *
   * Returns true if the specified point (x, y) is contained in the given rectangle.
   *
   * Parameters:
   *
   * bounds - <mxRectangle> that represents the area.
   * x - X-coordinate of the point.
   * y - Y-coordinate of the point.
   */
  contains: function (bounds, x, y) {
    return bounds.x <= x && bounds.x + bounds.width >= x && bounds.y <= y && bounds.y + bounds.height >= y;
  },

  /**
   * Function: intersects
   *
   * Returns true if the two rectangles intersect.
   *
   * Parameters:
   *
   * a - <mxRectangle> to be checked for intersection.
   * b - <mxRectangle> to be checked for intersection.
   */
  intersects: function (a, b) {
    var tw = a.width;
    var th = a.height;
    var rw = b.width;
    var rh = b.height;

    if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0) {
      return false;
    }

    var tx = a.x;
    var ty = a.y;
    var rx = b.x;
    var ry = b.y;

    rw += rx;
    rh += ry;
    tw += tx;
    th += ty;

    return (rw < rx || rw > tx) && (rh < ry || rh > ty) && (tw < tx || tw > rx) && (th < ty || th > ry);
  },

  /**
   * Function: intersectsHotspot
   *
   * Returns true if the state and the hotspot intersect.
   *
   * Parameters:
   *
   * state - <mxCellState>
   * x - X-coordinate.
   * y - Y-coordinate.
   * hotspot - Optional size of the hostpot.
   * min - Optional min size of the hostpot.
   * max - Optional max size of the hostpot.
   */
  intersectsHotspot: function (state, x, y, hotspot, min, max) {
    hotspot = hotspot != null ? hotspot : 1;
    min = min != null ? min : 0;
    max = max != null ? max : 0;

    if (hotspot > 0) {
      var cx = state.getCenterX();
      var cy = state.getCenterY();
      var w = state.width;
      var h = state.height;

      var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE) * state.view.scale;

      if (start > 0) {
        if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true)) {
          cy = state.y + start / 2;
          h = start;
        } else {
          cx = state.x + start / 2;
          w = start;
        }
      }

      w = Math.max(min, w * hotspot);
      h = Math.max(min, h * hotspot);

      if (max > 0) {
        w = Math.min(w, max);
        h = Math.min(h, max);
      }

      var rect = new mxRectangle(cx - w / 2, cy - h / 2, w, h);
      var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);

      if (alpha != 0) {
        var cos = Math.cos(-alpha);
        var sin = Math.sin(-alpha);
        var cx = new mxPoint(state.getCenterX(), state.getCenterY());
        var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);
        x = pt.x;
        y = pt.y;
      }

      return mxUtils.contains(rect, x, y);
    }

    return true;
  },

  /**
   * Function: getOffset
   *
   * Returns the offset for the specified container as an <mxPoint>. The
   * offset is the distance from the top left corner of the container to the
   * top left corner of the document.
   *
   * Parameters:
   *
   * container - DOM node to return the offset for.
   * scollOffset - Optional boolean to add the scroll offset of the document.
   * Default is false.
   */
  getOffset: function (container, scrollOffset, withOffsetParent = false) {
    var offsetLeft = 0;
    var offsetTop = 0;

    if (withOffsetParent) {
      offsetLeft = container.offsetLeft;
      offsetTop = container.offsetTop;
    } else {
      // Ignores document scroll origin for fixed elements
      var fixed = false;
      var node = container;
      var b = document.body;
      var d = document.documentElement;

      while (node != null && node != b && node != d && !fixed) {
        var style = mxUtils.getCurrentStyle(node);

        if (style != null) {
          fixed = fixed || style.position == 'fixed';
        }

        node = node.parentNode;
      }

      if (!scrollOffset && !fixed) {
        var offset = mxUtils.getDocumentScrollOrigin(container.ownerDocument);
        offsetLeft += offset.x;
        offsetTop += offset.y;
      }

      var r = container.getBoundingClientRect();

      if (r != null) {
        offsetLeft += r.left;
        offsetTop += r.top;
      }
    }

    return new mxPoint(offsetLeft, offsetTop);
  },

  /**
   * Function: getDocumentScrollOrigin
   *
   * Returns the scroll origin of the given document or the current document
   * if no document is given.
   */
  getDocumentScrollOrigin: function (doc) {
    if (mxClient.IS_QUIRKS) {
      return new mxPoint(doc.body.scrollLeft, doc.body.scrollTop);
    } else {
      var wnd = doc.defaultView || doc.parentWindow;

      var x =
        wnd != null && window.pageXOffset !== undefined
          ? window.pageXOffset
          : (document.documentElement || document.body.parentNode || document.body).scrollLeft;
      var y =
        wnd != null && window.pageYOffset !== undefined
          ? window.pageYOffset
          : (document.documentElement || document.body.parentNode || document.body).scrollTop;

      return new mxPoint(x, y);
    }
  },

  /**
   * Function: getScrollOrigin
   *
   * Returns the top, left corner of the viewrect as an <mxPoint>.
   *
   * Parameters:
   *
   * node - DOM node whose scroll origin should be returned.
   * includeAncestors - Whether the scroll origin of the ancestors should be
   * included. Default is false.
   * includeDocument - Whether the scroll origin of the document should be
   * included. Default is true.
   */
  getScrollOrigin: function (node, includeAncestors, includeDocument) {
    includeAncestors = includeAncestors != null ? includeAncestors : false;
    includeDocument = includeDocument != null ? includeDocument : true;

    var doc = node != null ? node.ownerDocument : document;
    var b = doc.body;
    var d = doc.documentElement;
    var result = new mxPoint();
    var fixed = false;

    while (node != null && node != b && node != d) {
      if (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop)) {
        result.x += node.scrollLeft;
        result.y += node.scrollTop;
      }

      var style = mxUtils.getCurrentStyle(node);

      if (style != null) {
        fixed = fixed || style.position == 'fixed';
      }

      node = includeAncestors ? node.parentNode : null;
    }

    if (!fixed && includeDocument) {
      var origin = mxUtils.getDocumentScrollOrigin(doc);

      result.x += origin.x;
      result.y += origin.y;
    }

    return result;
  },

  /**
   * Function: convertPoint
   *
   * Converts the specified point (x, y) using the offset of the specified
   * container and returns a new <mxPoint> with the result.
   *
   * (code)
   * var pt = mxUtils.convertPoint(graph.container,
   *   mxEvent.getClientX(evt), mxEvent.getClientY(evt));
   * (end)
   *
   * Parameters:
   *
   * container - DOM node to use for the offset.
   * x - X-coordinate of the point to be converted.
   * y - Y-coordinate of the point to be converted.
   */
  convertPoint: function (container, x, y) {
    var origin = mxUtils.getScrollOrigin(container, false);
    var offset = mxUtils.getOffset(container);

    offset.x -= origin.x;
    offset.y -= origin.y;

    return new mxPoint(x - offset.x, y - offset.y);
  },

  /**
   * Function: ltrim
   *
   * Strips all whitespaces from the beginning of the string. Without the
   * second parameter, this will trim these characters:
   *
   * - " " (ASCII 32 (0x20)), an ordinary space
   * - "\t" (ASCII 9 (0x09)), a tab
   * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
   * - "\r" (ASCII 13 (0x0D)), a carriage return
   * - "\0" (ASCII 0 (0x00)), the NUL-byte
   * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
   */
  ltrim: function (str, chars) {
    chars = chars || '\\s';

    return str != null ? str.replace(new RegExp('^[' + chars + ']+', 'g'), '') : null;
  },

  /**
   * Function: rtrim
   *
   * Strips all whitespaces from the end of the string. Without the second
   * parameter, this will trim these characters:
   *
   * - " " (ASCII 32 (0x20)), an ordinary space
   * - "\t" (ASCII 9 (0x09)), a tab
   * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
   * - "\r" (ASCII 13 (0x0D)), a carriage return
   * - "\0" (ASCII 0 (0x00)), the NUL-byte
   * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
   */
  rtrim: function (str, chars) {
    chars = chars || '\\s';

    return str != null ? str.replace(new RegExp('[' + chars + ']+$', 'g'), '') : null;
  },

  /**
   * Function: trim
   *
   * Strips all whitespaces from both end of the string.
   * Without the second parameter, Javascript function will trim these
   * characters:
   *
   * - " " (ASCII 32 (0x20)), an ordinary space
   * - "\t" (ASCII 9 (0x09)), a tab
   * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
   * - "\r" (ASCII 13 (0x0D)), a carriage return
   * - "\0" (ASCII 0 (0x00)), the NUL-byte
   * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
   */
  trim: function (str, chars) {
    return mxUtils.ltrim(mxUtils.rtrim(str, chars), chars);
  },

  /**
   * Function: isNumeric
   *
   * Returns true if the specified value is numeric, that is, if it is not
   * null, not an empty string, not a HEX number and isNaN returns false.
   *
   * Parameters:
   *
   * n - String representing the possibly numeric value.
   */
  isNumeric: function (n) {
    return !isNaN(parseFloat(n)) && isFinite(n) && (typeof n != 'string' || n.toLowerCase().indexOf('0x') < 0);
  },

  /**
   * Function: isInteger
   *
   * Returns true if the given value is an valid integer number.
   *
   * Parameters:
   *
   * n - String representing the possibly numeric value.
   */
  isInteger: function (n) {
    return String(parseInt(n)) === String(n);
  },

  /**
   * Function: mod
   *
   * Returns the remainder of division of n by m. You should use this instead
   * of the built-in operation as the built-in operation does not properly
   * handle negative numbers.
   */
  mod: function (n, m) {
    return ((n % m) + m) % m;
  },

  /**
   * Function: intersection
   *
   * Returns the intersection of two lines as an <mxPoint>.
   *
   * Parameters:
   *
   * x0 - X-coordinate of the first line's startpoint.
   * y0 - X-coordinate of the first line's startpoint.
   * x1 - X-coordinate of the first line's endpoint.
   * y1 - Y-coordinate of the first line's endpoint.
   * x2 - X-coordinate of the second line's startpoint.
   * y2 - Y-coordinate of the second line's startpoint.
   * x3 - X-coordinate of the second line's endpoint.
   * y3 - Y-coordinate of the second line's endpoint.
   */
  intersection: function (x0, y0, x1, y1, x2, y2, x3, y3) {
    var denom = (y3 - y2) * (x1 - x0) - (x3 - x2) * (y1 - y0);
    var nume_a = (x3 - x2) * (y0 - y2) - (y3 - y2) * (x0 - x2);
    var nume_b = (x1 - x0) * (y0 - y2) - (y1 - y0) * (x0 - x2);

    var ua = nume_a / denom;
    var ub = nume_b / denom;

    if (ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0) {
      // Get the intersection point
      var x = x0 + ua * (x1 - x0);
      var y = y0 + ua * (y1 - y0);

      return new mxPoint(x, y);
    }

    // No intersection
    return null;
  },

  /**
   * Function: ptSegDistSq
   *
   * Returns the square distance between a segment and a point. To get the
   * distance between a point and a line (with infinite length) use
   * <mxUtils.ptLineDist>.
   *
   * Parameters:
   *
   * x1 - X-coordinate of the startpoint of the segment.
   * y1 - Y-coordinate of the startpoint of the segment.
   * x2 - X-coordinate of the endpoint of the segment.
   * y2 - Y-coordinate of the endpoint of the segment.
   * px - X-coordinate of the point.
   * py - Y-coordinate of the point.
   */
  ptSegDistSq: function (x1, y1, x2, y2, px, py) {
    x2 -= x1;
    y2 -= y1;

    px -= x1;
    py -= y1;

    var dotprod = px * x2 + py * y2;
    var projlenSq;

    if (dotprod <= 0.0) {
      projlenSq = 0.0;
    } else {
      px = x2 - px;
      py = y2 - py;
      dotprod = px * x2 + py * y2;

      if (dotprod <= 0.0) {
        projlenSq = 0.0;
      } else {
        projlenSq = (dotprod * dotprod) / (x2 * x2 + y2 * y2);
      }
    }

    var lenSq = px * px + py * py - projlenSq;

    if (lenSq < 0) {
      lenSq = 0;
    }

    return lenSq;
  },

  /**
   * Function: ptLineDist
   *
   * Returns the distance between a line defined by two points and a point.
   * To get the distance between a point and a segment (with a specific
   * length) use <mxUtils.ptSeqDistSq>.
   *
   * Parameters:
   *
   * x1 - X-coordinate of point 1 of the line.
   * y1 - Y-coordinate of point 1 of the line.
   * x2 - X-coordinate of point 1 of the line.
   * y2 - Y-coordinate of point 1 of the line.
   * px - X-coordinate of the point.
   * py - Y-coordinate of the point.
   */
  ptLineDist: function (x1, y1, x2, y2, px, py) {
    return (
      Math.abs((y2 - y1) * px - (x2 - x1) * py + x2 * y1 - y2 * x1) /
      Math.sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1))
    );
  },

  /**
   * Function: relativeCcw
   *
   * Returns 1 if the given point on the right side of the segment, 0 if its
   * on the segment, and -1 if the point is on the left side of the segment.
   *
   * Parameters:
   *
   * x1 - X-coordinate of the startpoint of the segment.
   * y1 - Y-coordinate of the startpoint of the segment.
   * x2 - X-coordinate of the endpoint of the segment.
   * y2 - Y-coordinate of the endpoint of the segment.
   * px - X-coordinate of the point.
   * py - Y-coordinate of the point.
   */
  relativeCcw: function (x1, y1, x2, y2, px, py) {
    x2 -= x1;
    y2 -= y1;
    px -= x1;
    py -= y1;
    var ccw = px * y2 - py * x2;

    if (ccw == 0.0) {
      ccw = px * x2 + py * y2;

      if (ccw > 0.0) {
        px -= x2;
        py -= y2;
        ccw = px * x2 + py * y2;

        if (ccw < 0.0) {
          ccw = 0.0;
        }
      }
    }

    return ccw < 0.0 ? -1 : ccw > 0.0 ? 1 : 0;
  },

  /**
   * Function: animateChanges
   *
   * See <mxEffects.animateChanges>. This is for backwards compatibility and
   * will be removed later.
   */
  animateChanges: function (graph, changes) {
    // LATER: Deprecated, remove this function
    mxEffects.animateChanges.apply(this, arguments);
  },

  /**
   * Function: cascadeOpacity
   *
   * See <mxEffects.cascadeOpacity>. This is for backwards compatibility and
   * will be removed later.
   */
  cascadeOpacity: function (graph, cell, opacity) {
    mxEffects.cascadeOpacity.apply(this, arguments);
  },

  /**
   * Function: fadeOut
   *
   * See <mxEffects.fadeOut>. This is for backwards compatibility and
   * will be removed later.
   */
  fadeOut: function (node, from, remove, step, delay, isEnabled) {
    mxEffects.fadeOut.apply(this, arguments);
  },

  /**
   * Function: setOpacity
   *
   * Sets the opacity of the specified DOM node to the given value in %.
   *
   * Parameters:
   *
   * node - DOM node to set the opacity for.
   * value - Opacity in %. Possible values are between 0 and 100.
   */
  setOpacity: function (node, value) {
    if (mxUtils.isVml(node)) {
      if (value >= 100) {
        node.style.filter = '';
      } else {
        // TODO: Why is the division by 5 needed in VML?
        node.style.filter = 'alpha(opacity=' + value / 5 + ')';
      }
    } else if (mxClient.IS_IE && (typeof document.documentMode === 'undefined' || document.documentMode < 9)) {
      if (value >= 100) {
        node.style.filter = '';
      } else {
        node.style.filter = 'alpha(opacity=' + value + ')';
      }
    } else {
      node.style.opacity = value / 100;
    }
  },

  /**
   * Function: createImage
   *
   * Creates and returns an image (IMG node) or VML image (v:image) in IE6 in
   * quirks mode.
   *
   * Parameters:
   *
   * src - URL that points to the image to be displayed.
   */
  createImage: function (src) {
    var imageNode = null;

    if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat') {
      imageNode = document.createElement(mxClient.VML_PREFIX + ':image');
      imageNode.setAttribute('src', src);
      imageNode.style.borderStyle = 'none';
    } else {
      imageNode = document.createElement('img');
      imageNode.setAttribute('src', src);
      imageNode.setAttribute('border', '0');
    }

    return imageNode;
  },

  /**
   * Function: sortCells
   *
   * Sorts the given cells according to the order in the cell hierarchy.
   * Ascending is optional and defaults to true.
   */
  sortCells: function (cells, ascending) {
    ascending = ascending != null ? ascending : true;
    var lookup = new mxDictionary();
    cells.sort(function (o1, o2) {
      var p1 = lookup.get(o1);

      if (p1 == null) {
        p1 = mxCellPath.create(o1).split(mxCellPath.PATH_SEPARATOR);
        lookup.put(o1, p1);
      }

      var p2 = lookup.get(o2);

      if (p2 == null) {
        p2 = mxCellPath.create(o2).split(mxCellPath.PATH_SEPARATOR);
        lookup.put(o2, p2);
      }

      var comp = mxCellPath.compare(p1, p2);

      return comp == 0 ? 0 : comp > 0 == ascending ? 1 : -1;
    });

    return cells;
  },

  /**
   * Function: getStylename
   *
   * Returns the stylename in a style of the form [(stylename|key=value);] or
   * an empty string if the given style does not contain a stylename.
   *
   * Parameters:
   *
   * style - String of the form [(stylename|key=value);].
   */
  getStylename: function (style) {
    if (style != null) {
      var pairs = style.split(';');
      var stylename = pairs[0];

      if (stylename.indexOf('=') < 0) {
        return stylename;
      }
    }

    return '';
  },

  /**
   * Function: getStylenames
   *
   * Returns the stylenames in a style of the form [(stylename|key=value);]
   * or an empty array if the given style does not contain any stylenames.
   *
   * Parameters:
   *
   * style - String of the form [(stylename|key=value);].
   */
  getStylenames: function (style) {
    var result = [];

    if (style != null) {
      var pairs = style.split(';');

      for (var i = 0; i < pairs.length; i++) {
        if (pairs[i].indexOf('=') < 0) {
          result.push(pairs[i]);
        }
      }
    }

    return result;
  },

  /**
   * Function: indexOfStylename
   *
   * Returns the index of the given stylename in the given style. This
   * returns -1 if the given stylename does not occur (as a stylename) in the
   * given style, otherwise it returns the index of the first character.
   */
  indexOfStylename: function (style, stylename) {
    if (style != null && stylename != null) {
      var tokens = style.split(';');
      var pos = 0;

      for (var i = 0; i < tokens.length; i++) {
        if (tokens[i] == stylename) {
          return pos;
        }

        pos += tokens[i].length + 1;
      }
    }

    return -1;
  },

  /**
   * Function: addStylename
   *
   * Adds the specified stylename to the given style if it does not already
   * contain the stylename.
   */
  addStylename: function (style, stylename) {
    if (mxUtils.indexOfStylename(style, stylename) < 0) {
      if (style == null) {
        style = '';
      } else if (style.length > 0 && style.charAt(style.length - 1) != ';') {
        style += ';';
      }

      style += stylename;
    }

    return style;
  },

  /**
   * Function: removeStylename
   *
   * Removes all occurrences of the specified stylename in the given style
   * and returns the updated style. Trailing semicolons are not preserved.
   */
  removeStylename: function (style, stylename) {
    var result = [];

    if (style != null) {
      var tokens = style.split(';');

      for (var i = 0; i < tokens.length; i++) {
        if (tokens[i] != stylename) {
          result.push(tokens[i]);
        }
      }
    }

    return result.join(';');
  },

  /**
   * Function: removeAllStylenames
   *
   * Removes all stylenames from the given style and returns the updated
   * style.
   */
  removeAllStylenames: function (style) {
    var result = [];

    if (style != null) {
      var tokens = style.split(';');

      for (var i = 0; i < tokens.length; i++) {
        // Keeps the key, value assignments
        if (tokens[i].indexOf('=') >= 0) {
          result.push(tokens[i]);
        }
      }
    }

    return result.join(';');
  },

  /**
   * Function: setCellStyles
   *
   * Assigns the value for the given key in the styles of the given cells, or
   * removes the key from the styles if the value is null.
   *
   * Parameters:
   *
   * model - <mxGraphModel> to execute the transaction in.
   * cells - Array of <mxCells> to be updated.
   * key - Key of the style to be changed.
   * value - New value for the given key.
   */
  setCellStyles: function (model, cells, key, value) {
    if (cells != null && cells.length > 0) {
      model.beginUpdate();
      try {
        for (var i = 0; i < cells.length; i++) {
          if (cells[i] != null) {
            var style = mxUtils.setStyle(model.getStyle(cells[i]), key, value);
            model.setStyle(cells[i], style);
          }
        }
      } finally {
        model.endUpdate();
      }
    }
  },

  /**
   * Function: setStyle
   *
   * Adds or removes the given key, value pair to the style and returns the
   * new style. If value is null or zero length then the key is removed from
   * the style. This is for cell styles, not for CSS styles.
   *
   * Parameters:
   *
   * style - String of the form [(stylename|key=value);].
   * key - Key of the style to be changed.
   * value - New value for the given key.
   */
  setStyle: function (style, key, value) {
    var isValue = value != null && (typeof value.length == 'undefined' || value.length > 0);

    if (style == null || style.length == 0) {
      if (isValue) {
        style = key + '=' + value + ';';
      }
    } else {
      if (style.substring(0, key.length + 1) == key + '=') {
        var next = style.indexOf(';');

        if (isValue) {
          style = key + '=' + value + (next < 0 ? ';' : style.substring(next));
        } else {
          style = next < 0 || next == style.length - 1 ? '' : style.substring(next + 1);
        }
      } else {
        var index = style.indexOf(';' + key + '=');

        if (index < 0) {
          if (isValue) {
            var sep = style.charAt(style.length - 1) == ';' ? '' : ';';
            style = style + sep + key + '=' + value + ';';
          }
        } else {
          var next = style.indexOf(';', index + 1);

          if (isValue) {
            style = style.substring(0, index + 1) + key + '=' + value + (next < 0 ? ';' : style.substring(next));
          } else {
            style = style.substring(0, index) + (next < 0 ? ';' : style.substring(next));
          }
        }
      }
    }

    return style;
  },

  /**
   * Function: setCellStyleFlags
   *
   * Sets or toggles the flag bit for the given key in the cell's styles.
   * If value is null then the flag is toggled.
   *
   * Example:
   *
   * (code)
   * var cells = graph.getSelectionCells();
   * mxUtils.setCellStyleFlags(graph.model,
   * 			cells,
   * 			mxConstants.STYLE_FONTSTYLE,
   * 			mxConstants.FONT_BOLD);
   * (end)
   *
   * Toggles the bold font style.
   *
   * Parameters:
   *
   * model - <mxGraphModel> that contains the cells.
   * cells - Array of <mxCells> to change the style for.
   * key - Key of the style to be changed.
   * flag - Integer for the bit to be changed.
   * value - Optional boolean value for the flag.
   */
  setCellStyleFlags: function (model, cells, key, flag, value) {
    if (cells != null && cells.length > 0) {
      model.beginUpdate();
      try {
        for (var i = 0; i < cells.length; i++) {
          if (cells[i] != null) {
            var style = mxUtils.setStyleFlag(model.getStyle(cells[i]), key, flag, value);
            model.setStyle(cells[i], style);
          }
        }
      } finally {
        model.endUpdate();
      }
    }
  },

  /**
   * Function: setStyleFlag
   *
   * Sets or removes the given key from the specified style and returns the
   * new style. If value is null then the flag is toggled.
   *
   * Parameters:
   *
   * style - String of the form [(stylename|key=value);].
   * key - Key of the style to be changed.
   * flag - Integer for the bit to be changed.
   * value - Optional boolean value for the given flag.
   */
  setStyleFlag: function (style, key, flag, value) {
    if (style == null || style.length == 0) {
      if (value || value == null) {
        style = key + '=' + flag;
      } else {
        style = key + '=0';
      }
    } else {
      var index = style.indexOf(key + '=');

      if (index < 0) {
        var sep = style.charAt(style.length - 1) == ';' ? '' : ';';

        if (value || value == null) {
          style = style + sep + key + '=' + flag;
        } else {
          style = style + sep + key + '=0';
        }
      } else {
        var cont = style.indexOf(';', index);
        var tmp = '';

        if (cont < 0) {
          tmp = style.substring(index + key.length + 1);
        } else {
          tmp = style.substring(index + key.length + 1, cont);
        }

        if (value == null) {
          tmp = parseInt(tmp) ^ flag;
        } else if (value) {
          tmp = parseInt(tmp) | flag;
        } else {
          tmp = parseInt(tmp) & ~flag;
        }

        style = style.substring(0, index) + key + '=' + tmp + (cont >= 0 ? style.substring(cont) : '');
      }
    }

    return style;
  },

  /**
   * Function: getAlignmentAsPoint
   *
   * Returns an <mxPoint> that represents the horizontal and vertical alignment
   * for numeric computations. X is -0.5 for center, -1 for right and 0 for
   * left alignment. Y is -0.5 for middle, -1 for bottom and 0 for top
   * alignment. Default values for missing arguments is top, left.
   */
  getAlignmentAsPoint: function (align, valign) {
    var dx = -0.5;
    var dy = -0.5;

    // Horizontal alignment
    if (align == mxConstants.ALIGN_LEFT) {
      dx = 0;
    } else if (align == mxConstants.ALIGN_RIGHT) {
      dx = -1;
    }

    // Vertical alignment
    if (valign == mxConstants.ALIGN_TOP) {
      dy = 0;
    } else if (valign == mxConstants.ALIGN_BOTTOM) {
      dy = -1;
    }

    return new mxPoint(dx, dy);
  },

  /**
   * Function: getSizeForString
   *
   * Returns an <mxRectangle> with the size (width and height in pixels) of
   * the given string. The string may contain HTML markup. Newlines should be
   * converted to <br> before calling this method. The caller is responsible
   * for sanitizing the HTML markup.
   *
   * Example:
   *
   * (code)
   * var label = graph.getLabel(cell).replace(/\n/g, "<br>");
   * var size = graph.getSizeForString(label);
   * (end)
   *
   * Parameters:
   *
   * text - String whose size should be returned.
   * fontSize - Integer that specifies the font size in pixels. Default is
   * <mxConstants.DEFAULT_FONTSIZE>.
   * fontFamily - String that specifies the name of the font family. Default
   * is <mxConstants.DEFAULT_FONTFAMILY>.
   * textWidth - Optional width for text wrapping.
   * fontStyle - Optional font style.
   */
  getSizeForString: function (text, fontSize, fontFamily, textWidth, fontStyle) {
    fontSize = fontSize != null ? fontSize : mxConstants.DEFAULT_FONTSIZE;
    fontFamily = fontFamily != null ? fontFamily : mxConstants.DEFAULT_FONTFAMILY;
    var div = document.createElement('div');

    // Sets the font size and family
    div.style.fontFamily = fontFamily;
    div.style.fontSize = Math.round(fontSize) + 'px';
    div.style.lineHeight = Math.round(fontSize * mxConstants.LINE_HEIGHT) + 'px';

    // Sets the font style
    if (fontStyle != null) {
      if ((fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
        div.style.fontWeight = 'bold';
      }

      if ((fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
        div.style.fontStyle = 'italic';
      }

      var txtDecor = [];

      if ((fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
        txtDecor.push('underline');
      }

      if ((fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) {
        txtDecor.push('line-through');
      }

      if (txtDecor.length > 0) {
        div.style.textDecoration = txtDecor.join(' ');
      }
    }

    // Disables block layout and outside wrapping and hides the div
    div.style.position = 'absolute';
    div.style.visibility = 'hidden';
    div.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block';
    div.style.zoom = '1';

    if (textWidth != null) {
      div.style.width = textWidth + 'px';
      div.style.whiteSpace = 'normal';
    } else {
      div.style.whiteSpace = 'nowrap';
    }

    // Adds the text and inserts into DOM for updating of size
    div.innerHTML = text;
    document.body.appendChild(div);

    // Gets the size and removes from DOM
    var size = new mxRectangle(0, 0, div.offsetWidth, div.offsetHeight);
    document.body.removeChild(div);

    return size;
  },

  /**
   * Function: getViewXml
   */
  getViewXml: function (graph, scale, cells, x0, y0) {
    x0 = x0 != null ? x0 : 0;
    y0 = y0 != null ? y0 : 0;
    scale = scale != null ? scale : 1;

    if (cells == null) {
      var model = graph.getModel();
      cells = [model.getRoot()];
    }

    var view = graph.getView();
    var result = null;

    // Disables events on the view
    var eventsEnabled = view.isEventsEnabled();
    view.setEventsEnabled(false);

    // Workaround for label bounds not taken into account for image export.
    // Creates a temporary draw pane which is used for rendering the text.
    // Text rendering is required for finding the bounds of the labels.
    var drawPane = view.drawPane;
    var overlayPane = view.overlayPane;

    if (graph.dialect == mxConstants.DIALECT_SVG) {
      view.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
      view.canvas.appendChild(view.drawPane);

      // Redirects cell overlays into temporary container
      view.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
      view.canvas.appendChild(view.overlayPane);
    } else {
      view.drawPane = view.drawPane.cloneNode(false);
      view.canvas.appendChild(view.drawPane);

      // Redirects cell overlays into temporary container
      view.overlayPane = view.overlayPane.cloneNode(false);
      view.canvas.appendChild(view.overlayPane);
    }

    // Resets the translation
    var translate = view.getTranslate();
    view.translate = new mxPoint(x0, y0);

    // Creates the temporary cell states in the view
    var temp = new mxTemporaryCellStates(graph.getView(), scale, cells);

    try {
      var enc = new mxCodec();
      result = enc.encode(graph.getView());
    } finally {
      temp.destroy();
      view.translate = translate;
      view.canvas.removeChild(view.drawPane);
      view.canvas.removeChild(view.overlayPane);
      view.drawPane = drawPane;
      view.overlayPane = overlayPane;
      view.setEventsEnabled(eventsEnabled);
    }

    return result;
  },

  /**
   * Function: getScaleForPageCount
   *
   * Returns the scale to be used for printing the graph with the given
   * bounds across the specifies number of pages with the given format. The
   * scale is always computed such that it given the given amount or fewer
   * pages in the print output. See <mxPrintPreview> for an example.
   *
   * Parameters:
   *
   * pageCount - Specifies the number of pages in the print output.
   * graph - <mxGraph> that should be printed.
   * pageFormat - Optional <mxRectangle> that specifies the page format.
   * Default is <mxConstants.PAGE_FORMAT_A4_PORTRAIT>.
   * border - The border along each side of every page.
   */
  getScaleForPageCount: function (pageCount, graph, pageFormat, border) {
    if (pageCount < 1) {
      // We can't work with less than 1 page, return no scale
      // change
      return 1;
    }

    pageFormat = pageFormat != null ? pageFormat : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
    border = border != null ? border : 0;

    var availablePageWidth = pageFormat.width - border * 2;
    var availablePageHeight = pageFormat.height - border * 2;

    // Work out the number of pages required if the
    // graph is not scaled.
    var graphBounds = graph.getGraphBounds().clone();
    var sc = graph.getView().getScale();
    graphBounds.width /= sc;
    graphBounds.height /= sc;
    var graphWidth = graphBounds.width;
    var graphHeight = graphBounds.height;

    var scale = 1;

    // The ratio of the width/height for each printer page
    var pageFormatAspectRatio = availablePageWidth / availablePageHeight;
    // The ratio of the width/height for the graph to be printer
    var graphAspectRatio = graphWidth / graphHeight;

    // The ratio of horizontal pages / vertical pages for this
    // graph to maintain its aspect ratio on this page format
    var pagesAspectRatio = graphAspectRatio / pageFormatAspectRatio;

    // Factor the square root of the page count up and down
    // by the pages aspect ratio to obtain a horizontal and
    // vertical page count that adds up to the page count
    // and has the correct aspect ratio
    var pageRoot = Math.sqrt(pageCount);
    var pagesAspectRatioSqrt = Math.sqrt(pagesAspectRatio);
    var numRowPages = pageRoot * pagesAspectRatioSqrt;
    var numColumnPages = pageRoot / pagesAspectRatioSqrt;

    // These value are rarely more than 2 rounding downs away from
    // a total that meets the page count. In cases of one being less
    // than 1 page, the other value can be too high and take more iterations
    // In this case, just change that value to be the page count, since
    // we know the other value is 1
    if (numRowPages < 1 && numColumnPages > pageCount) {
      var scaleChange = numColumnPages / pageCount;
      numColumnPages = pageCount;
      numRowPages /= scaleChange;
    }

    if (numColumnPages < 1 && numRowPages > pageCount) {
      var scaleChange = numRowPages / pageCount;
      numRowPages = pageCount;
      numColumnPages /= scaleChange;
    }

    var currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);

    var numLoops = 0;

    // Iterate through while the rounded up number of pages comes to
    // a total greater than the required number
    while (currentTotalPages > pageCount) {
      // Round down the page count (rows or columns) that is
      // closest to its next integer down in percentage terms.
      // i.e. Reduce the page total by reducing the total
      // page area by the least possible amount

      var roundRowDownProportion = Math.floor(numRowPages) / numRowPages;
      var roundColumnDownProportion = Math.floor(numColumnPages) / numColumnPages;

      // If the round down proportion is, work out the proportion to
      // round down to 1 page less
      if (roundRowDownProportion == 1) {
        roundRowDownProportion = Math.floor(numRowPages - 1) / numRowPages;
      }
      if (roundColumnDownProportion == 1) {
        roundColumnDownProportion = Math.floor(numColumnPages - 1) / numColumnPages;
      }

      // Check which rounding down is smaller, but in the case of very small roundings
      // try the other dimension instead
      var scaleChange = 1;

      // Use the higher of the two values
      if (roundRowDownProportion > roundColumnDownProportion) {
        scaleChange = roundRowDownProportion;
      } else {
        scaleChange = roundColumnDownProportion;
      }

      numRowPages = numRowPages * scaleChange;
      numColumnPages = numColumnPages * scaleChange;
      currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);

      numLoops++;

      if (numLoops > 10) {
        break;
      }
    }

    // Work out the scale from the number of row pages required
    // The column pages will give the same value
    var posterWidth = availablePageWidth * numRowPages;
    scale = posterWidth / graphWidth;

    // Allow for rounding errors
    return scale * 0.99999;
  },

  /**
   * Function: show
   *
   * Copies the styles and the markup from the graph's container into the
   * given document and removes all cursor styles. The document is returned.
   *
   * This function should be called from within the document with the graph.
   * If you experience problems with missing stylesheets in IE then try adding
   * the domain to the trusted sites.
   *
   * Parameters:
   *
   * graph - <mxGraph> to be copied.
   * doc - Document where the new graph is created.
   * x0 - X-coordinate of the graph view origin. Default is 0.
   * y0 - Y-coordinate of the graph view origin. Default is 0.
   * w - Optional width of the graph view.
   * h - Optional height of the graph view.
   */
  show: function (graph, doc, x0, y0, w, h) {
    x0 = x0 != null ? x0 : 0;
    y0 = y0 != null ? y0 : 0;

    if (doc == null) {
      var wnd = window.open();
      doc = wnd.document;
    } else {
      doc.open();
    }

    // Workaround for missing print output in IE9 standards
    if (document.documentMode == 9) {
      doc.writeln('<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=9"><![endif]-->');
    }

    var bounds = graph.getGraphBounds();
    var dx = Math.ceil(x0 - bounds.x);
    var dy = Math.ceil(y0 - bounds.y);

    if (w == null) {
      w = Math.ceil(bounds.width + x0) + Math.ceil(Math.ceil(bounds.x) - bounds.x);
    }

    if (h == null) {
      h = Math.ceil(bounds.height + y0) + Math.ceil(Math.ceil(bounds.y) - bounds.y);
    }

    // Needs a special way of creating the page so that no click is required
    // to refresh the contents after the external CSS styles have been loaded.
    // To avoid a click or programmatic refresh, the styleSheets[].cssText
    // property is copied over from the original document.
    if (mxClient.IS_IE || document.documentMode == 11) {
      var html = '<html><head>';

      var base = document.getElementsByTagName('base');

      for (var i = 0; i < base.length; i++) {
        html += base[i].outerHTML;
      }

      html += '<style>';

      // Copies the stylesheets without having to load them again
      for (var i = 0; i < document.styleSheets.length; i++) {
        try {
          html += document.styleSheets[i].cssText;
        } catch (e) {
          // ignore security exception
        }
      }

      html += '</style></head><body style="margin:0px;">';

      // Copies the contents of the graph container
      html +=
        '<div style="position:absolute;overflow:hidden;width:' +
        w +
        'px;height:' +
        h +
        'px;"><div style="position:relative;left:' +
        dx +
        'px;top:' +
        dy +
        'px;">';
      html += graph.container.innerHTML;
      html += '</div></div></body><html>';

      doc.writeln(html);
      doc.close();
    } else {
      doc.writeln('<html><head>');

      var base = document.getElementsByTagName('base');

      for (var i = 0; i < base.length; i++) {
        doc.writeln(mxUtils.getOuterHtml(base[i]));
      }

      var links = document.getElementsByTagName('link');

      for (var i = 0; i < links.length; i++) {
        doc.writeln(mxUtils.getOuterHtml(links[i]));
      }

      var styles = document.getElementsByTagName('style');

      for (var i = 0; i < styles.length; i++) {
        doc.writeln(mxUtils.getOuterHtml(styles[i]));
      }

      doc.writeln('</head><body style="margin:0px;"></body></html>');
      doc.close();

      var outer = doc.createElement('div');
      outer.position = 'absolute';
      outer.overflow = 'hidden';
      outer.style.width = w + 'px';
      outer.style.height = h + 'px';

      // Required for HTML labels if foreignObjects are disabled
      var div = doc.createElement('div');
      div.style.position = 'absolute';
      div.style.left = dx + 'px';
      div.style.top = dy + 'px';

      var node = graph.container.firstChild;
      var svg = null;

      while (node != null) {
        var clone = node.cloneNode(true);

        if (node == graph.view.drawPane.ownerSVGElement) {
          outer.appendChild(clone);
          svg = clone;
        } else {
          div.appendChild(clone);
        }

        node = node.nextSibling;
      }

      doc.body.appendChild(outer);

      if (div.firstChild != null) {
        doc.body.appendChild(div);
      }

      if (svg != null) {
        svg.style.minWidth = '';
        svg.style.minHeight = '';
        svg.firstChild.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
      }
    }

    mxUtils.removeCursors(doc.body);

    return doc;
  },

  /**
   * Function: printScreen
   *
   * Prints the specified graph using a new window and the built-in print
   * dialog.
   *
   * This function should be called from within the document with the graph.
   *
   * Parameters:
   *
   * graph - <mxGraph> to be printed.
   */
  printScreen: function (graph) {
    var wnd = window.open();
    var bounds = graph.getGraphBounds();
    mxUtils.show(graph, wnd.document);

    var print = function () {
      wnd.focus();
      wnd.print();
      wnd.close();
    };

    // Workaround for Google Chrome which needs a bit of a
    // delay in order to render the SVG contents
    if (mxClient.IS_GC) {
      wnd.setTimeout(print, 500);
    } else {
      print();
    }
  },

  /**
   * Function: popup
   *
   * Shows the specified text content in a new <mxWindow> or a new browser
   * window if isInternalWindow is false.
   *
   * Parameters:
   *
   * content - String that specifies the text to be displayed.
   * isInternalWindow - Optional boolean indicating if an mxWindow should be
   * used instead of a new browser window. Default is false.
   */
  popup: function (content, isInternalWindow) {
    if (isInternalWindow) {
      var div = document.createElement('div');

      div.style.overflow = 'scroll';
      div.style.width = '636px';
      div.style.height = '460px';

      var pre = document.createElement('pre');
      pre.innerHTML = mxUtils.htmlEntities(content, false).replace(/\n/g, '<br>').replace(/ /g, '&nbsp;');

      div.appendChild(pre);

      var w = document.body.clientWidth;
      var h = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight);
      var wnd = new mxWindow('Popup Window', div, w / 2 - 320, h / 2 - 240, 640, 480, false, true);

      wnd.setClosable(true);
      wnd.setVisible(true);
    } else {
      // Wraps up the XML content in a textarea
      if (mxClient.IS_NS) {
        var wnd = window.open();
        wnd.document.writeln('<pre>' + mxUtils.htmlEntities(content) + '</pre');
        wnd.document.close();
      } else {
        var wnd = window.open();
        var pre = wnd.document.createElement('pre');
        pre.innerHTML = mxUtils.htmlEntities(content, false).replace(/\n/g, '<br>').replace(/ /g, '&nbsp;');
        wnd.document.body.appendChild(pre);
      }
    }
  },

  /**
   * Function: alert
   *
   * Displayss the given alert in a new dialog. This implementation uses the
   * built-in alert function. This is used to display validation errors when
   * connections cannot be changed or created.
   *
   * Parameters:
   *
   * message - String specifying the message to be displayed.
   */
  alert: function (message) {
    alert(message);
  },

  /**
   * Function: prompt
   *
   * Displays the given message in a prompt dialog. This implementation uses
   * the built-in prompt function.
   *
   * Parameters:
   *
   * message - String specifying the message to be displayed.
   * defaultValue - Optional string specifying the default value.
   */
  prompt: function (message, defaultValue) {
    return prompt(message, defaultValue != null ? defaultValue : '');
  },

  /**
   * Function: confirm
   *
   * Displays the given message in a confirm dialog. This implementation uses
   * the built-in confirm function.
   *
   * Parameters:
   *
   * message - String specifying the message to be displayed.
   */
  confirm: function (message) {
    return confirm(message);
  },

  /**
   * Function: error
   *
   * Displays the given error message in a new <mxWindow> of the given width.
   * If close is true then an additional close button is added to the window.
   * The optional icon specifies the icon to be used for the window. Default
   * is <mxUtils.errorImage>.
   *
   * Parameters:
   *
   * message - String specifying the message to be displayed.
   * width - Integer specifying the width of the window.
   * close - Optional boolean indicating whether to add a close button.
   * icon - Optional icon for the window decoration.
   */
  error: function (message, width, close, icon) {
    var div = document.createElement('div');
    div.style.padding = '20px';

    var img = document.createElement('img');
    img.setAttribute('src', icon || mxUtils.errorImage);
    img.setAttribute('valign', 'bottom');
    img.style.verticalAlign = 'middle';
    div.appendChild(img);

    div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
    div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
    div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
    mxUtils.write(div, message);

    var w = document.body.clientWidth;
    var h = document.body.clientHeight || document.documentElement.clientHeight;
    var warn = new mxWindow(
      mxResources.get(mxUtils.errorResource) || mxUtils.errorResource,
      div,
      (w - width) / 2,
      h / 4,
      width,
      null,
      false,
      true
    );

    if (close) {
      mxUtils.br(div);

      var tmp = document.createElement('p');
      var button = document.createElement('button');

      if (mxClient.IS_IE) {
        button.style.cssText = 'float:right';
      } else {
        button.setAttribute('style', 'float:right');
      }

      mxEvent.addListener(button, 'click', function (evt) {
        warn.destroy();
      });

      mxUtils.write(button, mxResources.get(mxUtils.closeResource) || mxUtils.closeResource);

      tmp.appendChild(button);
      div.appendChild(tmp);

      mxUtils.br(div);

      warn.setClosable(true);
    }

    warn.setVisible(true);

    return warn;
  },

  /**
   * Function: makeDraggable
   *
   * Configures the given DOM element to act as a drag source for the
   * specified graph. Returns a a new <mxDragSource>. If
   * <mxDragSource.guideEnabled> is enabled then the x and y arguments must
   * be used in funct to match the preview location.
   *
   * Example:
   *
   * (code)
   * var funct = function(graph, evt, cell, x, y)
   * {
   *   if (graph.canImportCell(cell))
   *   {
   *     var parent = graph.getDefaultParent();
   *     var vertex = null;
   *
   *     graph.getModel().beginUpdate();
   *     try
   *     {
   * 	     vertex = graph.insertVertex(parent, null, 'Hello', x, y, 80, 30);
   *     }
   *     finally
   *     {
   *       graph.getModel().endUpdate();
   *     }
   *
   *     graph.setSelectionCell(vertex);
   *   }
   * }
   *
   * var img = document.createElement('img');
   * img.setAttribute('src', 'editors/images/rectangle.gif');
   * img.style.position = 'absolute';
   * img.style.left = '0px';
   * img.style.top = '0px';
   * img.style.width = '16px';
   * img.style.height = '16px';
   *
   * var dragImage = img.cloneNode(true);
   * dragImage.style.width = '32px';
   * dragImage.style.height = '32px';
   * mxUtils.makeDraggable(img, graph, funct, dragImage);
   * document.body.appendChild(img);
   * (end)
   *
   * Parameters:
   *
   * element - DOM element to make draggable.
   * graphF - <mxGraph> that acts as the drop target or a function that takes a
   * mouse event and returns the current <mxGraph>.
   * funct - Function to execute on a successful drop.
   * dragElement - Optional DOM node to be used for the drag preview.
   * dx - Optional horizontal offset between the cursor and the drag
   * preview.
   * dy - Optional vertical offset between the cursor and the drag
   * preview.
   * autoscroll - Optional boolean that specifies if autoscroll should be
   * used. Default is mxGraph.autoscroll.
   * scalePreview - Optional boolean that specifies if the preview element
   * should be scaled according to the graph scale. If this is true, then
   * the offsets will also be scaled. Default is false.
   * highlightDropTargets - Optional boolean that specifies if dropTargets
   * should be highlighted. Default is true.
   * getDropTarget - Optional function to return the drop target for a given
   * location (x, y). Default is mxGraph.getCellAt.
   */
  makeDraggable: function (
    element,
    graphF,
    funct,
    dragElement,
    dx,
    dy,
    autoscroll,
    scalePreview,
    highlightDropTargets,
    getDropTarget
  ) {
    var dragSource = new mxDragSource(element, funct);
    dragSource.dragOffset = new mxPoint(dx != null ? dx : 0, dy != null ? dy : mxConstants.TOOLTIP_VERTICAL_OFFSET);
    dragSource.autoscroll = autoscroll;

    // Cannot enable this by default. This needs to be enabled in the caller
    // if the funct argument uses the new x- and y-arguments.
    dragSource.setGuidesEnabled(false);

    if (highlightDropTargets != null) {
      dragSource.highlightDropTargets = highlightDropTargets;
    }

    // Overrides function to find drop target cell
    if (getDropTarget != null) {
      dragSource.getDropTarget = getDropTarget;
    }

    // Overrides function to get current graph
    dragSource.getGraphForEvent = function (evt) {
      return typeof graphF == 'function' ? graphF(evt) : graphF;
    };

    // Translates switches into dragSource customizations
    if (dragElement != null) {
      dragSource.createDragElement = function () {
        return dragElement.cloneNode(true);
      };

      if (scalePreview) {
        dragSource.createPreviewElement = function (graph) {
          var elt = dragElement.cloneNode(true);

          var w = parseInt(elt.style.width);
          var h = parseInt(elt.style.height);
          elt.style.width = Math.round(w * graph.view.scale) + 'px';
          elt.style.height = Math.round(h * graph.view.scale) + 'px';

          return elt;
        };
      }
    }

    return dragSource;
  },
};

/**
 * util/mxConstants.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxConstants = {
  /**
   * Class: mxConstants
   *
   * Defines various global constants.
   *
   * Variable: DEFAULT_HOTSPOT
   *
   * Defines the portion of the cell which is to be used as a connectable
   * region. Default is 0.3. Possible values are 0 < x <= 1.
   */
  DEFAULT_HOTSPOT: 0.3,

  /**
   * Variable: MIN_HOTSPOT_SIZE
   *
   * Defines the minimum size in pixels of the portion of the cell which is
   * to be used as a connectable region. Default is 8.
   */
  MIN_HOTSPOT_SIZE: 8,

  /**
   * Variable: MAX_HOTSPOT_SIZE
   *
   * Defines the maximum size in pixels of the portion of the cell which is
   * to be used as a connectable region. Use 0 for no maximum. Default is 0.
   */
  MAX_HOTSPOT_SIZE: 0,

  /**
   * Variable: RENDERING_HINT_EXACT
   *
   * Defines the exact rendering hint.
   */
  RENDERING_HINT_EXACT: 'exact',

  /**
   * Variable: RENDERING_HINT_FASTER
   *
   * Defines the faster rendering hint.
   */
  RENDERING_HINT_FASTER: 'faster',

  /**
   * Variable: RENDERING_HINT_FASTEST
   *
   * Defines the fastest rendering hint.
   */
  RENDERING_HINT_FASTEST: 'fastest',

  /**
   * Variable: DIALECT_SVG
   *
   * Defines the SVG display dialect name.
   */
  DIALECT_SVG: 'svg',

  /**
   * Variable: DIALECT_VML
   *
   * Defines the VML display dialect name.
   */
  DIALECT_VML: 'vml',

  /**
   * Variable: DIALECT_MIXEDHTML
   *
   * Defines the mixed HTML display dialect name.
   */
  DIALECT_MIXEDHTML: 'mixedHtml',

  /**
   * Variable: DIALECT_PREFERHTML
   *
   * Defines the preferred HTML display dialect name.
   */
  DIALECT_PREFERHTML: 'preferHtml',

  /**
   * Variable: DIALECT_STRICTHTML
   *
   * Defines the strict HTML display dialect.
   */
  DIALECT_STRICTHTML: 'strictHtml',

  /**
   * Variable: NS_SVG
   *
   * Defines the SVG namespace.
   */
  NS_SVG: 'http://www.w3.org/2000/svg',

  /**
   * Variable: NS_XHTML
   *
   * Defines the XHTML namespace.
   */
  NS_XHTML: 'http://www.w3.org/1999/xhtml',

  /**
   * Variable: NS_XLINK
   *
   * Defines the XLink namespace.
   */
  NS_XLINK: 'http://www.w3.org/1999/xlink',

  /**
   * Variable: SHADOWCOLOR
   *
   * Defines the color to be used to draw shadows in shapes and windows.
   * Default is gray.
   */
  SHADOWCOLOR: 'gray',

  /**
   * Variable: VML_SHADOWCOLOR
   *
   * Used for shadow color in filters where transparency is not supported
   * (Microsoft Internet Explorer). Default is gray.
   */
  VML_SHADOWCOLOR: 'gray',

  /**
   * Variable: SHADOW_OFFSET_X
   *
   * Specifies the x-offset of the shadow. Default is 2.
   */
  SHADOW_OFFSET_X: 2,

  /**
   * Variable: SHADOW_OFFSET_Y
   *
   * Specifies the y-offset of the shadow. Default is 3.
   */
  SHADOW_OFFSET_Y: 3,

  /**
   * Variable: SHADOW_OPACITY
   *
   * Defines the opacity for shadows. Default is 1.
   */
  SHADOW_OPACITY: 1,

  /**
   * Variable: NODETYPE_ELEMENT
   *
   * DOM node of type ELEMENT.
   */
  NODETYPE_ELEMENT: 1,

  /**
   * Variable: NODETYPE_ATTRIBUTE
   *
   * DOM node of type ATTRIBUTE.
   */
  NODETYPE_ATTRIBUTE: 2,

  /**
   * Variable: NODETYPE_TEXT
   *
   * DOM node of type TEXT.
   */
  NODETYPE_TEXT: 3,

  /**
   * Variable: NODETYPE_CDATA
   *
   * DOM node of type CDATA.
   */
  NODETYPE_CDATA: 4,

  /**
   * Variable: NODETYPE_ENTITY_REFERENCE
   *
   * DOM node of type ENTITY_REFERENCE.
   */
  NODETYPE_ENTITY_REFERENCE: 5,

  /**
   * Variable: NODETYPE_ENTITY
   *
   * DOM node of type ENTITY.
   */
  NODETYPE_ENTITY: 6,

  /**
   * Variable: NODETYPE_PROCESSING_INSTRUCTION
   *
   * DOM node of type PROCESSING_INSTRUCTION.
   */
  NODETYPE_PROCESSING_INSTRUCTION: 7,

  /**
   * Variable: NODETYPE_COMMENT
   *
   * DOM node of type COMMENT.
   */
  NODETYPE_COMMENT: 8,

  /**
   * Variable: NODETYPE_DOCUMENT
   *
   * DOM node of type DOCUMENT.
   */
  NODETYPE_DOCUMENT: 9,

  /**
   * Variable: NODETYPE_DOCUMENTTYPE
   *
   * DOM node of type DOCUMENTTYPE.
   */
  NODETYPE_DOCUMENTTYPE: 10,

  /**
   * Variable: NODETYPE_DOCUMENT_FRAGMENT
   *
   * DOM node of type DOCUMENT_FRAGMENT.
   */
  NODETYPE_DOCUMENT_FRAGMENT: 11,

  /**
   * Variable: NODETYPE_NOTATION
   *
   * DOM node of type NOTATION.
   */
  NODETYPE_NOTATION: 12,

  /**
   * Variable: TOOLTIP_VERTICAL_OFFSET
   *
   * Defines the vertical offset for the tooltip.
   * Default is 16.
   */
  TOOLTIP_VERTICAL_OFFSET: 16,

  /**
   * Variable: DEFAULT_VALID_COLOR
   *
   * Specifies the default valid color. Default is #0000FF.
   */
  DEFAULT_VALID_COLOR: '#00FF00',

  /**
   * Variable: DEFAULT_INVALID_COLOR
   *
   * Specifies the default invalid color. Default is #FF0000.
   */
  DEFAULT_INVALID_COLOR: '#FF0000',

  /**
   * Variable: OUTLINE_HIGHLIGHT_COLOR
   *
   * Specifies the default highlight color for shape outlines.
   * Default is #0000FF. This is used in <mxEdgeHandler>.
   */
  OUTLINE_HIGHLIGHT_COLOR: '#00FF00',

  /**
   * Variable: OUTLINE_HIGHLIGHT_COLOR
   *
   * Defines the strokewidth to be used for shape outlines.
   * Default is 5. This is used in <mxEdgeHandler>.
   */
  OUTLINE_HIGHLIGHT_STROKEWIDTH: 5,

  /**
   * Variable: HIGHLIGHT_STROKEWIDTH
   *
   * Defines the strokewidth to be used for the highlights.
   * Default is 3.
   */
  HIGHLIGHT_STROKEWIDTH: 3,

  /**
   * Variable: CONSTRAINT_HIGHLIGHT_SIZE
   *
   * Size of the constraint highlight (in px). Default is 2.
   */
  HIGHLIGHT_SIZE: 2,

  /**
   * Variable: HIGHLIGHT_OPACITY
   *
   * Opacity (in %) used for the highlights (including outline).
   * Default is 100.
   */
  HIGHLIGHT_OPACITY: 100,

  /**
   * Variable: CURSOR_MOVABLE_VERTEX
   *
   * Defines the cursor for a movable vertex. Default is 'move'.
   */
  CURSOR_MOVABLE_VERTEX: 'move',

  /**
   * Variable: CURSOR_MOVABLE_EDGE
   *
   * Defines the cursor for a movable edge. Default is 'move'.
   */
  CURSOR_MOVABLE_EDGE: 'move',

  /**
   * Variable: CURSOR_LABEL_HANDLE
   *
   * Defines the cursor for a movable label. Default is 'default'.
   */
  CURSOR_LABEL_HANDLE: 'default',

  /**
   * Variable: CURSOR_TERMINAL_HANDLE
   *
   * Defines the cursor for a terminal handle. Default is 'pointer'.
   */
  CURSOR_TERMINAL_HANDLE: 'pointer',

  /**
   * Variable: CURSOR_BEND_HANDLE
   *
   * Defines the cursor for a movable bend. Default is 'crosshair'.
   */
  CURSOR_BEND_HANDLE: 'crosshair',

  /**
   * Variable: CURSOR_VIRTUAL_BEND_HANDLE
   *
   * Defines the cursor for a movable bend. Default is 'crosshair'.
   */
  CURSOR_VIRTUAL_BEND_HANDLE: 'crosshair',

  /**
   * Variable: CURSOR_CONNECT
   *
   * Defines the cursor for a connectable state. Default is 'pointer'.
   */
  CURSOR_CONNECT: 'pointer',

  /**
   * Variable: HIGHLIGHT_COLOR
   *
   * Defines the color to be used for the cell highlighting.
   * Use 'none' for no color. Default is #00FF00.
   */
  HIGHLIGHT_COLOR: '#00FF00',

  /**
   * Variable: TARGET_HIGHLIGHT_COLOR
   *
   * Defines the color to be used for highlighting a target cell for a new
   * or changed connection. Note that this may be either a source or
   * target terminal in the graph. Use 'none' for no color.
   * Default is #0000FF.
   */
  CONNECT_TARGET_COLOR: '#0000FF',

  /**
   * Variable: INVALID_CONNECT_TARGET_COLOR
   *
   * Defines the color to be used for highlighting a invalid target cells
   * for a new or changed connections. Note that this may be either a source
   * or target terminal in the graph. Use 'none' for no color. Default is
   * #FF0000.
   */
  INVALID_CONNECT_TARGET_COLOR: '#FF0000',

  /**
   * Variable: DROP_TARGET_COLOR
   *
   * Defines the color to be used for the highlighting target parent cells
   * (for drag and drop). Use 'none' for no color. Default is #0000FF.
   */
  DROP_TARGET_COLOR: '#0000FF',

  /**
   * Variable: VALID_COLOR
   *
   * Defines the color to be used for the coloring valid connection
   * previews. Use 'none' for no color. Default is #FF0000.
   */
  VALID_COLOR: '#00FF00',

  /**
   * Variable: INVALID_COLOR
   *
   * Defines the color to be used for the coloring invalid connection
   * previews. Use 'none' for no color. Default is #FF0000.
   */
  INVALID_COLOR: '#FF0000',

  /**
   * Variable: EDGE_SELECTION_COLOR
   *
   * Defines the color to be used for the selection border of edges. Use
   * 'none' for no color. Default is #00FF00.
   */
  EDGE_SELECTION_COLOR: '#00FF00',

  /**
   * Variable: VERTEX_SELECTION_COLOR
   *
   * Defines the color to be used for the selection border of vertices. Use
   * 'none' for no color. Default is #00FF00.
   */
  VERTEX_SELECTION_COLOR: '#00FF00',

  /**
   * Variable: VERTEX_SELECTION_STROKEWIDTH
   *
   * Defines the strokewidth to be used for vertex selections.
   * Default is 1.
   */
  VERTEX_SELECTION_STROKEWIDTH: 1,

  /**
   * Variable: EDGE_SELECTION_STROKEWIDTH
   *
   * Defines the strokewidth to be used for edge selections.
   * Default is 1.
   */
  EDGE_SELECTION_STROKEWIDTH: 1,

  /**
   * Variable: SELECTION_DASHED
   *
   * Defines the dashed state to be used for the vertex selection
   * border. Default is true.
   */
  VERTEX_SELECTION_DASHED: true,

  /**
   * Variable: SELECTION_DASHED
   *
   * Defines the dashed state to be used for the edge selection
   * border. Default is true.
   */
  EDGE_SELECTION_DASHED: true,

  /**
   * Variable: GUIDE_COLOR
   *
   * Defines the color to be used for the guidelines in mxGraphHandler.
   * Default is #FF0000.
   */
  GUIDE_COLOR: '#FF0000',

  /**
   * Variable: GUIDE_STROKEWIDTH
   *
   * Defines the strokewidth to be used for the guidelines in mxGraphHandler.
   * Default is 1.
   */
  GUIDE_STROKEWIDTH: 1,

  /**
   * Variable: OUTLINE_COLOR
   *
   * Defines the color to be used for the outline rectangle
   * border.  Use 'none' for no color. Default is #0099FF.
   */
  OUTLINE_COLOR: '#0099FF',

  /**
   * Variable: OUTLINE_STROKEWIDTH
   *
   * Defines the strokewidth to be used for the outline rectangle
   * stroke width. Default is 3.
   */
  OUTLINE_STROKEWIDTH: mxClient.IS_IE ? 2 : 3,

  /**
   * Variable: HANDLE_SIZE
   *
   * Defines the default size for handles. Default is 6.
   */
  HANDLE_SIZE: 6,

  /**
   * Variable: LABEL_HANDLE_SIZE
   *
   * Defines the default size for label handles. Default is 4.
   */
  LABEL_HANDLE_SIZE: 4,

  /**
   * Variable: HANDLE_FILLCOLOR
   *
   * Defines the color to be used for the handle fill color. Use 'none' for
   * no color. Default is #00FF00 (green).
   */
  HANDLE_FILLCOLOR: '#00FF00',

  /**
   * Variable: HANDLE_STROKECOLOR
   *
   * Defines the color to be used for the handle stroke color. Use 'none' for
   * no color. Default is black.
   */
  HANDLE_STROKECOLOR: 'black',

  /**
   * Variable: LABEL_HANDLE_FILLCOLOR
   *
   * Defines the color to be used for the label handle fill color. Use 'none'
   * for no color. Default is yellow.
   */
  LABEL_HANDLE_FILLCOLOR: 'yellow',

  /**
   * Variable: CONNECT_HANDLE_FILLCOLOR
   *
   * Defines the color to be used for the connect handle fill color. Use
   * 'none' for no color. Default is #0000FF (blue).
   */
  CONNECT_HANDLE_FILLCOLOR: '#0000FF',

  /**
   * Variable: LOCKED_HANDLE_FILLCOLOR
   *
   * Defines the color to be used for the locked handle fill color. Use
   * 'none' for no color. Default is #FF0000 (red).
   */
  LOCKED_HANDLE_FILLCOLOR: '#FF0000',

  /**
   * Variable: OUTLINE_HANDLE_FILLCOLOR
   *
   * Defines the color to be used for the outline sizer fill color. Use
   * 'none' for no color. Default is #00FFFF.
   */
  OUTLINE_HANDLE_FILLCOLOR: '#00FFFF',

  /**
   * Variable: OUTLINE_HANDLE_STROKECOLOR
   *
   * Defines the color to be used for the outline sizer stroke color. Use
   * 'none' for no color. Default is #0033FF.
   */
  OUTLINE_HANDLE_STROKECOLOR: '#0033FF',

  /**
   * Variable: DEFAULT_FONTFAMILY
   *
   * Defines the default family for all fonts. Default is Arial,Helvetica.
   */
  DEFAULT_FONTFAMILY: 'Arial,Helvetica',

  /**
   * Variable: DEFAULT_FONTSIZE
   *
   * Defines the default size (in px). Default is 11.
   */
  DEFAULT_FONTSIZE: 11,

  /**
   * Variable: DEFAULT_TEXT_DIRECTION
   *
   * Defines the default value for the <STYLE_TEXT_DIRECTION> if no value is
   * defined for it in the style. Default value is an empty string which means
   * the default system setting is used and no direction is set.
   */
  DEFAULT_TEXT_DIRECTION: '',

  /**
   * Variable: LINE_HEIGHT
   *
   * Defines the default line height for text labels. Default is 1.2.
   */
  LINE_HEIGHT: 1.2,

  /**
   * Variable: WORD_WRAP
   *
   * Defines the CSS value for the word-wrap property. Default is "normal".
   * Change this to "break-word" to allow long words to be able to be broken
   * and wrap onto the next line.
   */
  WORD_WRAP: 'normal',

  /**
   * Variable: ABSOLUTE_LINE_HEIGHT
   *
   * Specifies if absolute line heights should be used (px) in CSS. Default
   * is false. Set this to true for backwards compatibility.
   */
  ABSOLUTE_LINE_HEIGHT: false,

  /**
   * Variable: DEFAULT_FONTSTYLE
   *
   * Defines the default style for all fonts. Default is 0. This can be set
   * to any combination of font styles as follows.
   *
   * (code)
   * mxConstants.DEFAULT_FONTSTYLE = mxConstants.FONT_BOLD | mxConstants.FONT_ITALIC;
   * (end)
   */
  DEFAULT_FONTSTYLE: 0,

  /**
   * Variable: DEFAULT_STARTSIZE
   *
   * Defines the default start size for swimlanes. Default is 40.
   */
  DEFAULT_STARTSIZE: 40,

  /**
   * Variable: DEFAULT_MARKERSIZE
   *
   * Defines the default size for all markers. Default is 6.
   */
  DEFAULT_MARKERSIZE: 6,

  /**
   * Variable: DEFAULT_IMAGESIZE
   *
   * Defines the default width and height for images used in the
   * label shape. Default is 24.
   */
  DEFAULT_IMAGESIZE: 24,

  /**
   * Variable: ENTITY_SEGMENT
   *
   * Defines the length of the horizontal segment of an Entity Relation.
   * This can be overridden using <mxConstants.STYLE_SEGMENT> style.
   * Default is 30.
   */
  ENTITY_SEGMENT: 30,

  /**
   * Variable: RECTANGLE_ROUNDING_FACTOR
   *
   * Defines the rounding factor for rounded rectangles in percent between
   * 0 and 1. Values should be smaller than 0.5. Default is 0.15.
   */
  RECTANGLE_ROUNDING_FACTOR: 0.15,

  /**
   * Variable: LINE_ARCSIZE
   *
   * Defines the size of the arcs for rounded edges. Default is 20.
   */
  LINE_ARCSIZE: 20,

  /**
   * Variable: ARROW_SPACING
   *
   * Defines the spacing between the arrow shape and its terminals. Default is 0.
   */
  ARROW_SPACING: 0,

  /**
   * Variable: ARROW_WIDTH
   *
   * Defines the width of the arrow shape. Default is 30.
   */
  ARROW_WIDTH: 30,

  /**
   * Variable: ARROW_SIZE
   *
   * Defines the size of the arrowhead in the arrow shape. Default is 30.
   */
  ARROW_SIZE: 30,

  /**
   * Variable: PAGE_FORMAT_A4_PORTRAIT
   *
   * Defines the rectangle for the A4 portrait page format. The dimensions
   * of this page format are 826x1169 pixels.
   */
  PAGE_FORMAT_A4_PORTRAIT: new mxRectangle(0, 0, 827, 1169),

  /**
   * Variable: PAGE_FORMAT_A4_PORTRAIT
   *
   * Defines the rectangle for the A4 portrait page format. The dimensions
   * of this page format are 826x1169 pixels.
   */
  PAGE_FORMAT_A4_LANDSCAPE: new mxRectangle(0, 0, 1169, 827),

  /**
   * Variable: PAGE_FORMAT_LETTER_PORTRAIT
   *
   * Defines the rectangle for the Letter portrait page format. The
   * dimensions of this page format are 850x1100 pixels.
   */
  PAGE_FORMAT_LETTER_PORTRAIT: new mxRectangle(0, 0, 850, 1100),

  /**
   * Variable: PAGE_FORMAT_LETTER_PORTRAIT
   *
   * Defines the rectangle for the Letter portrait page format. The dimensions
   * of this page format are 850x1100 pixels.
   */
  PAGE_FORMAT_LETTER_LANDSCAPE: new mxRectangle(0, 0, 1100, 850),

  /**
   * Variable: NONE
   *
   * Defines the value for none. Default is "none".
   */
  NONE: 'none',

  /**
   * Variable: STYLE_PERIMETER
   *
   * Defines the key for the perimeter style. This is a function that defines
   * the perimeter around a particular shape. Possible values are the
   * functions defined in <mxPerimeter>. Alternatively, the constants in this
   * class that start with "PERIMETER_" may be used to access
   * perimeter styles in <mxStyleRegistry>. Value is "perimeter".
   */
  STYLE_PERIMETER: 'perimeter',

  /**
   * Variable: STYLE_SOURCE_PORT
   *
   * Defines the ID of the cell that should be used for computing the
   * perimeter point of the source for an edge. This allows for graphically
   * connecting to a cell while keeping the actual terminal of the edge.
   * Value is "sourcePort".
   */
  STYLE_SOURCE_PORT: 'sourcePort',

  /**
   * Variable: STYLE_TARGET_PORT
   *
   * Defines the ID of the cell that should be used for computing the
   * perimeter point of the target for an edge. This allows for graphically
   * connecting to a cell while keeping the actual terminal of the edge.
   * Value is "targetPort".
   */
  STYLE_TARGET_PORT: 'targetPort',

  /**
   * Variable: STYLE_PORT_CONSTRAINT
   *
   * Defines the direction(s) that edges are allowed to connect to cells in.
   * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH,
   * DIRECTION_EAST" and "DIRECTION_WEST". Value is
   * "portConstraint".
   */
  STYLE_PORT_CONSTRAINT: 'portConstraint',

  /**
   * Variable: STYLE_PORT_CONSTRAINT_ROTATION
   *
   * Define whether port constraint directions are rotated with vertex
   * rotation. 0 (default) causes port constraints to remain absolute,
   * relative to the graph, 1 causes the constraints to rotate with
   * the vertex. Value is "portConstraintRotation".
   */
  STYLE_PORT_CONSTRAINT_ROTATION: 'portConstraintRotation',

  /**
   * Variable: STYLE_SOURCE_PORT_CONSTRAINT
   *
   * Defines the direction(s) that edges are allowed to connect to sources in.
   * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST"
   * and "DIRECTION_WEST". Value is "sourcePortConstraint".
   */
  STYLE_SOURCE_PORT_CONSTRAINT: 'sourcePortConstraint',

  /**
   * Variable: STYLE_TARGET_PORT_CONSTRAINT
   *
   * Defines the direction(s) that edges are allowed to connect to targets in.
   * Possible values are "DIRECTION_NORTH, DIRECTION_SOUTH, DIRECTION_EAST"
   * and "DIRECTION_WEST". Value is "targetPortConstraint".
   */
  STYLE_TARGET_PORT_CONSTRAINT: 'targetPortConstraint',

  /**
   * Variable: STYLE_OPACITY
   *
   * Defines the key for the opacity style. The type of the value is
   * numeric and the possible range is 0-100. Value is "opacity".
   */
  STYLE_OPACITY: 'opacity',

  /**
   * Variable: STYLE_FILL_OPACITY
   *
   * Defines the key for the fill opacity style. The type of the value is
   * numeric and the possible range is 0-100. Value is "fillOpacity".
   */
  STYLE_FILL_OPACITY: 'fillOpacity',

  /**
   * Variable: STYLE_STROKE_OPACITY
   *
   * Defines the key for the stroke opacity style. The type of the value is
   * numeric and the possible range is 0-100. Value is "strokeOpacity".
   */
  STYLE_STROKE_OPACITY: 'strokeOpacity',

  /**
   * Variable: STYLE_TEXT_OPACITY
   *
   * Defines the key for the text opacity style. The type of the value is
   * numeric and the possible range is 0-100. Value is "textOpacity".
   */
  STYLE_TEXT_OPACITY: 'textOpacity',

  /**
   * Variable: STYLE_TEXT_DIRECTION
   *
   * Defines the key for the text direction style. Possible values are
   * "TEXT_DIRECTION_DEFAULT, TEXT_DIRECTION_AUTO, TEXT_DIRECTION_LTR"
   * and "TEXT_DIRECTION_RTL". Value is "textDirection".
   * The default value for the style is defined in <DEFAULT_TEXT_DIRECTION>.
   * It is used is no value is defined for this key in a given style. This is
   * an experimental style that is currently ignored in the backends.
   */
  STYLE_TEXT_DIRECTION: 'textDirection',

  /**
   * Variable: STYLE_OVERFLOW
   *
   * Defines the key for the overflow style. Possible values are 'visible',
   * 'hidden', 'fill' and 'width'. The default value is 'visible'. This value
   * specifies how overlapping vertex labels are handled. A value of
   * 'visible' will show the complete label. A value of 'hidden' will clip
   * the label so that it does not overlap the vertex bounds. A value of
   * 'fill' will use the vertex bounds and a value of 'width' will use the
   * vertex width for the label. See <mxGraph.isLabelClipped>. Note that
   * the vertical alignment is ignored for overflow fill and for horizontal
   * alignment, left should be used to avoid pixel offsets in Internet Explorer
   * 11 and earlier or if foreignObjects are disabled. Value is "overflow".
   */
  STYLE_OVERFLOW: 'overflow',

  /**
   * Variable: STYLE_ORTHOGONAL
   *
   * Defines if the connection points on either end of the edge should be
   * computed so that the edge is vertical or horizontal if possible and
   * if the point is not at a fixed location. Default is false. This is
   * used in <mxGraph.isOrthogonal>, which also returns true if the edgeStyle
   * of the edge is an elbow or entity. Value is "orthogonal".
   */
  STYLE_ORTHOGONAL: 'orthogonal',

  /**
   * Variable: STYLE_EXIT_X
   *
   * Defines the key for the horizontal relative coordinate connection point
   * of an edge with its source terminal. Value is "exitX".
   */
  STYLE_EXIT_X: 'exitX',

  /**
   * Variable: STYLE_EXIT_Y
   *
   * Defines the key for the vertical relative coordinate connection point
   * of an edge with its source terminal. Value is "exitY".
   */
  STYLE_EXIT_Y: 'exitY',

  /**
   * Variable: STYLE_EXIT_DX
   *
   * Defines the key for the horizontal offset of the connection point
   * of an edge with its source terminal. Value is "exitDx".
   */
  STYLE_EXIT_DX: 'exitDx',

  /**
   * Variable: STYLE_EXIT_DY
   *
   * Defines the key for the vertical offset of the connection point
   * of an edge with its source terminal. Value is "exitDy".
   */
  STYLE_EXIT_DY: 'exitDy',

  /**
   * Variable: STYLE_EXIT_PERIMETER
   *
   * Defines if the perimeter should be used to find the exact entry point
   * along the perimeter of the source. Possible values are 0 (false) and
   * 1 (true). Default is 1 (true). Value is "exitPerimeter".
   */
  STYLE_EXIT_PERIMETER: 'exitPerimeter',

  /**
   * Variable: STYLE_ENTRY_X
   *
   * Defines the key for the horizontal relative coordinate connection point
   * of an edge with its target terminal. Value is "entryX".
   */
  STYLE_ENTRY_X: 'entryX',

  /**
   * Variable: STYLE_ENTRY_Y
   *
   * Defines the key for the vertical relative coordinate connection point
   * of an edge with its target terminal. Value is "entryY".
   */
  STYLE_ENTRY_Y: 'entryY',

  /**
   * Variable: STYLE_ENTRY_DX
   *
   * Defines the key for the horizontal offset of the connection point
   * of an edge with its target terminal. Value is "entryDx".
   */
  STYLE_ENTRY_DX: 'entryDx',

  /**
   * Variable: STYLE_ENTRY_DY
   *
   * Defines the key for the vertical offset of the connection point
   * of an edge with its target terminal. Value is "entryDy".
   */
  STYLE_ENTRY_DY: 'entryDy',

  /**
   * Variable: STYLE_ENTRY_PERIMETER
   *
   * Defines if the perimeter should be used to find the exact entry point
   * along the perimeter of the target. Possible values are 0 (false) and
   * 1 (true). Default is 1 (true). Value is "entryPerimeter".
   */
  STYLE_ENTRY_PERIMETER: 'entryPerimeter',

  /**
   * Variable: STYLE_WHITE_SPACE
   *
   * Defines the key for the white-space style. Possible values are 'nowrap'
   * and 'wrap'. The default value is 'nowrap'. This value specifies how
   * white-space inside a HTML vertex label should be handled. A value of
   * 'nowrap' means the text will never wrap to the next line until a
   * linefeed is encountered. A value of 'wrap' means text will wrap when
   * necessary. This style is only used for HTML labels.
   * See <mxGraph.isWrapping>. Value is "whiteSpace".
   */
  STYLE_WHITE_SPACE: 'whiteSpace',

  /**
   * Variable: STYLE_ROTATION
   *
   * Defines the key for the rotation style. The type of the value is
   * numeric and the possible range is 0-360. Value is "rotation".
   */
  STYLE_ROTATION: 'rotation',

  /**
   * Variable: STYLE_FILLCOLOR
   *
   * Defines the key for the fill color. Possible values are all HTML color
   * names or HEX codes, as well as special keywords such as 'swimlane,
   * 'inherit' or 'indicated' to use the color code of a related cell or the
   * indicator shape. Value is "fillColor".
   */
  STYLE_FILLCOLOR: 'fillColor',

  /**
   * Variable: STYLE_POINTER_EVENTS
   *
   * Specifies if pointer events should be fired on transparent backgrounds.
   * This style is currently only supported in <mxRectangleShape>. Default
   * is true. Value is "pointerEvents". This is typically set to
   * false in groups where the transparent part should allow any underlying
   * cells to be clickable.
   */
  STYLE_POINTER_EVENTS: 'pointerEvents',

  /**
   * Variable: STYLE_SWIMLANE_FILLCOLOR
   *
   * Defines the key for the fill color of the swimlane background. Possible
   * values are all HTML color names or HEX codes. Default is no background.
   * Value is "swimlaneFillColor".
   */
  STYLE_SWIMLANE_FILLCOLOR: 'swimlaneFillColor',

  /**
   * Variable: STYLE_MARGIN
   *
   * Defines the key for the margin between the ellipses in the double ellipse shape.
   * Possible values are all positive numbers. Value is "margin".
   */
  STYLE_MARGIN: 'margin',

  /**
   * Variable: STYLE_GRADIENTCOLOR
   *
   * Defines the key for the gradient color. Possible values are all HTML color
   * names or HEX codes, as well as special keywords such as 'swimlane,
   * 'inherit' or 'indicated' to use the color code of a related cell or the
   * indicator shape. This is ignored if no fill color is defined. Value is
   * "gradientColor".
   */
  STYLE_GRADIENTCOLOR: 'gradientColor',

  /**
   * Variable: STYLE_GRADIENT_DIRECTION
   *
   * Defines the key for the gradient direction. Possible values are
   * <DIRECTION_EAST>, <DIRECTION_WEST>, <DIRECTION_NORTH> and
   * <DIRECTION_SOUTH>. Default is <DIRECTION_SOUTH>. Generally, and by
   * default in mxGraph, gradient painting is done from the value of
   * <STYLE_FILLCOLOR> to the value of <STYLE_GRADIENTCOLOR>. Taking the
   * example of <DIRECTION_NORTH>, this means <STYLE_FILLCOLOR> color at the
   * bottom of paint pattern and <STYLE_GRADIENTCOLOR> at top, with a
   * gradient in-between. Value is "gradientDirection".
   */
  STYLE_GRADIENT_DIRECTION: 'gradientDirection',

  /**
   * Variable: STYLE_STROKECOLOR
   *
   * Defines the key for the strokeColor style. Possible values are all HTML
   * color names or HEX codes, as well as special keywords such as 'swimlane,
   * 'inherit', 'indicated' to use the color code of a related cell or the
   * indicator shape or 'none' for no color. Value is "strokeColor".
   */
  STYLE_STROKECOLOR: 'strokeColor',

  /**
   * Variable: STYLE_SEPARATORCOLOR
   *
   * Defines the key for the separatorColor style. Possible values are all
   * HTML color names or HEX codes. This style is only used for
   * <SHAPE_SWIMLANE> shapes. Value is "separatorColor".
   */
  STYLE_SEPARATORCOLOR: 'separatorColor',

  /**
   * Variable: STYLE_STROKEWIDTH
   *
   * Defines the key for the strokeWidth style. The type of the value is
   * numeric and the possible range is any non-negative value larger or equal
   * to 1. The value defines the stroke width in pixels. Note: To hide a
   * stroke use strokeColor none. Value is "strokeWidth".
   */
  STYLE_STROKEWIDTH: 'strokeWidth',

  /**
   * Variable: STYLE_ALIGN
   *
   * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
   * <ALIGN_CENTER> and <ALIGN_RIGHT>. This value defines how the lines of
   * the label are horizontally aligned. <ALIGN_LEFT> mean label text lines
   * are aligned to left of the label bounds, <ALIGN_RIGHT> to the right of
   * the label bounds and <ALIGN_CENTER> means the center of the text lines
   * are aligned in the center of the label bounds. Note this value doesn't
   * affect the positioning of the overall label bounds relative to the
   * vertex, to move the label bounds horizontally, use
   * <STYLE_LABEL_POSITION>. Value is "align".
   */
  STYLE_ALIGN: 'align',

  /**
   * Variable: STYLE_VERTICAL_ALIGN
   *
   * Defines the key for the verticalAlign style. Possible values are
   * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. This value defines how
   * the lines of the label are vertically aligned. <ALIGN_TOP> means the
   * topmost label text line is aligned against the top of the label bounds,
   * <ALIGN_BOTTOM> means the bottom-most label text line is aligned against
   * the bottom of the label bounds and <ALIGN_MIDDLE> means there is equal
   * spacing between the topmost text label line and the top of the label
   * bounds and the bottom-most text label line and the bottom of the label
   * bounds. Note this value doesn't affect the positioning of the overall
   * label bounds relative to the vertex, to move the label bounds
   * vertically, use <STYLE_VERTICAL_LABEL_POSITION>. Value is "verticalAlign".
   */
  STYLE_VERTICAL_ALIGN: 'verticalAlign',

  /**
   * Variable: STYLE_LABEL_WIDTH
   *
   * Defines the key for the width of the label if the label position is not
   * center. Value is "labelWidth".
   */
  STYLE_LABEL_WIDTH: 'labelWidth',

  /**
   * Variable: STYLE_LABEL_POSITION
   *
   * Defines the key for the horizontal label position of vertices. Possible
   * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>. Default is
   * <ALIGN_CENTER>. The label align defines the position of the label
   * relative to the cell. <ALIGN_LEFT> means the entire label bounds is
   * placed completely just to the left of the vertex, <ALIGN_RIGHT> means
   * adjust to the right and <ALIGN_CENTER> means the label bounds are
   * vertically aligned with the bounds of the vertex. Note this value
   * doesn't affect the positioning of label within the label bounds, to move
   * the label horizontally within the label bounds, use <STYLE_ALIGN>.
   * Value is "labelPosition".
   */
  STYLE_LABEL_POSITION: 'labelPosition',

  /**
   * Variable: STYLE_VERTICAL_LABEL_POSITION
   *
   * Defines the key for the vertical label position of vertices. Possible
   * values are <ALIGN_TOP>, <ALIGN_BOTTOM> and <ALIGN_MIDDLE>. Default is
   * <ALIGN_MIDDLE>. The label align defines the position of the label
   * relative to the cell. <ALIGN_TOP> means the entire label bounds is
   * placed completely just on the top of the vertex, <ALIGN_BOTTOM> means
   * adjust on the bottom and <ALIGN_MIDDLE> means the label bounds are
   * horizontally aligned with the bounds of the vertex. Note this value
   * doesn't affect the positioning of label within the label bounds, to move
   * the label vertically within the label bounds, use
   * <STYLE_VERTICAL_ALIGN>. Value is "verticalLabelPosition".
   */
  STYLE_VERTICAL_LABEL_POSITION: 'verticalLabelPosition',

  /**
   * Variable: STYLE_IMAGE_ASPECT
   *
   * Defines the key for the image aspect style. Possible values are 0 (do
   * not preserve aspect) or 1 (keep aspect). This is only used in
   * <mxImageShape>. Default is 1. Value is "imageAspect".
   */
  STYLE_IMAGE_ASPECT: 'imageAspect',

  /**
   * Variable: STYLE_IMAGE_ALIGN
   *
   * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
   * <ALIGN_CENTER> and <ALIGN_RIGHT>. The value defines how any image in the
   * vertex label is aligned horizontally within the label bounds of a
   * <SHAPE_LABEL> shape. Value is "imageAlign".
   */
  STYLE_IMAGE_ALIGN: 'imageAlign',

  /**
   * Variable: STYLE_IMAGE_VERTICAL_ALIGN
   *
   * Defines the key for the verticalAlign style. Possible values are
   * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. The value defines how
   * any image in the vertex label is aligned vertically within the label
   * bounds of a <SHAPE_LABEL> shape. Value is "imageVerticalAlign".
   */
  STYLE_IMAGE_VERTICAL_ALIGN: 'imageVerticalAlign',

  /**
   * Variable: STYLE_GLASS
   *
   * Defines the key for the glass style. Possible values are 0 (disabled) and
   * 1(enabled). The default value is 0. This is used in <mxLabel>. Value is
   * "glass".
   */
  STYLE_GLASS: 'glass',

  /**
   * Variable: STYLE_IMAGE
   *
   * Defines the key for the image style. Possible values are any image URL,
   * the type of the value is String. This is the path to the image that is
   * to be displayed within the label of a vertex. Data URLs should use the
   * following format: data:image/png,xyz where xyz is the base64 encoded
   * data (without the "base64"-prefix). Note that Data URLs are only
   * supported in modern browsers. Value is "image".
   */
  STYLE_IMAGE: 'image',

  /**
   * Variable: STYLE_IMAGE_WIDTH
   *
   * Defines the key for the imageWidth style. The type of this value is
   * int, the value is the image width in pixels and must be greater than 0.
   * Value is "imageWidth".
   */
  STYLE_IMAGE_WIDTH: 'imageWidth',

  /**
   * Variable: STYLE_IMAGE_HEIGHT
   *
   * Defines the key for the imageHeight style. The type of this value is
   * int, the value is the image height in pixels and must be greater than 0.
   * Value is "imageHeight".
   */
  STYLE_IMAGE_HEIGHT: 'imageHeight',

  /**
   * Variable: STYLE_IMAGE_BACKGROUND
   *
   * Defines the key for the image background color. This style is only used
   * in <mxImageShape>. Possible values are all HTML color names or HEX
   * codes. Value is "imageBackground".
   */
  STYLE_IMAGE_BACKGROUND: 'imageBackground',

  /**
   * Variable: STYLE_IMAGE_BORDER
   *
   * Defines the key for the image border color. This style is only used in
   * <mxImageShape>. Possible values are all HTML color names or HEX codes.
   * Value is "imageBorder".
   */
  STYLE_IMAGE_BORDER: 'imageBorder',

  /**
   * Variable: STYLE_FLIPH
   *
   * Defines the key for the horizontal image flip. This style is only used
   * in <mxImageShape>. Possible values are 0 and 1. Default is 0. Value is
   * "flipH".
   */
  STYLE_FLIPH: 'flipH',

  /**
   * Variable: STYLE_FLIPV
   *
   * Defines the key for the vertical flip. Possible values are 0 and 1.
   * Default is 0. Value is "flipV".
   */
  STYLE_FLIPV: 'flipV',

  /**
   * Variable: STYLE_NOLABEL
   *
   * Defines the key for the noLabel style. If this is true then no label is
   * visible for a given cell. Possible values are true or false (1 or 0).
   * Default is false. Value is "noLabel".
   */
  STYLE_NOLABEL: 'noLabel',

  /**
   * Variable: STYLE_NOEDGESTYLE
   *
   * Defines the key for the noEdgeStyle style. If this is true then no edge
   * style is applied for a given edge. Possible values are true or false
   * (1 or 0). Default is false. Value is "noEdgeStyle".
   */
  STYLE_NOEDGESTYLE: 'noEdgeStyle',

  /**
   * Variable: STYLE_LABEL_BACKGROUNDCOLOR
   *
   * Defines the key for the label background color. Possible values are all
   * HTML color names or HEX codes. Value is "labelBackgroundColor".
   */
  STYLE_LABEL_BACKGROUNDCOLOR: 'labelBackgroundColor',

  /**
   * Variable: STYLE_LABEL_BORDERCOLOR
   *
   * Defines the key for the label border color. Possible values are all
   * HTML color names or HEX codes. Value is "labelBorderColor".
   */
  STYLE_LABEL_BORDERCOLOR: 'labelBorderColor',

  /**
   * Variable: STYLE_LABEL_PADDING
   *
   * Defines the key for the label padding, ie. the space between the label
   * border and the label. Value is "labelPadding".
   */
  STYLE_LABEL_PADDING: 'labelPadding',

  /**
   * Variable: STYLE_INDICATOR_SHAPE
   *
   * Defines the key for the indicator shape used within an <mxLabel>.
   * Possible values are all SHAPE_* constants or the names of any new
   * shapes. The indicatorShape has precedence over the indicatorImage.
   * Value is "indicatorShape".
   */
  STYLE_INDICATOR_SHAPE: 'indicatorShape',

  /**
   * Variable: STYLE_INDICATOR_IMAGE
   *
   * Defines the key for the indicator image used within an <mxLabel>.
   * Possible values are all image URLs. The indicatorShape has
   * precedence over the indicatorImage. Value is "indicatorImage".
   */
  STYLE_INDICATOR_IMAGE: 'indicatorImage',

  /**
   * Variable: STYLE_INDICATOR_COLOR
   *
   * Defines the key for the indicatorColor style. Possible values are all
   * HTML color names or HEX codes, as well as the special 'swimlane' keyword
   * to refer to the color of the parent swimlane if one exists. Value is
   * "indicatorColor".
   */
  STYLE_INDICATOR_COLOR: 'indicatorColor',

  /**
   * Variable: STYLE_INDICATOR_STROKECOLOR
   *
   * Defines the key for the indicator stroke color in <mxLabel>.
   * Possible values are all color codes. Value is "indicatorStrokeColor".
   */
  STYLE_INDICATOR_STROKECOLOR: 'indicatorStrokeColor',

  /**
   * Variable: STYLE_INDICATOR_GRADIENTCOLOR
   *
   * Defines the key for the indicatorGradientColor style. Possible values
   * are all HTML color names or HEX codes. This style is only supported in
   * <SHAPE_LABEL> shapes. Value is "indicatorGradientColor".
   */
  STYLE_INDICATOR_GRADIENTCOLOR: 'indicatorGradientColor',

  /**
   * Variable: STYLE_INDICATOR_SPACING
   *
   * The defines the key for the spacing between the label and the
   * indicator in <mxLabel>. Possible values are in pixels. Value is
   * "indicatorSpacing".
   */
  STYLE_INDICATOR_SPACING: 'indicatorSpacing',

  /**
   * Variable: STYLE_INDICATOR_WIDTH
   *
   * Defines the key for the indicator width. Possible values start at 0 (in
   * pixels). Value is "indicatorWidth".
   */
  STYLE_INDICATOR_WIDTH: 'indicatorWidth',

  /**
   * Variable: STYLE_INDICATOR_HEIGHT
   *
   * Defines the key for the indicator height. Possible values start at 0 (in
   * pixels). Value is "indicatorHeight".
   */
  STYLE_INDICATOR_HEIGHT: 'indicatorHeight',

  /**
   * Variable: STYLE_INDICATOR_DIRECTION
   *
   * Defines the key for the indicatorDirection style. The direction style is
   * used to specify the direction of certain shapes (eg. <mxTriangle>).
   * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
   * <DIRECTION_NORTH> and <DIRECTION_SOUTH>. Value is "indicatorDirection".
   */
  STYLE_INDICATOR_DIRECTION: 'indicatorDirection',

  /**
   * Variable: STYLE_SHADOW
   *
   * Defines the key for the shadow style. The type of the value is Boolean.
   * Value is "shadow".
   */
  STYLE_SHADOW: 'shadow',

  /**
   * Variable: STYLE_SEGMENT
   *
   * Defines the key for the segment style. The type of this value is float
   * and the value represents the size of the horizontal segment of the
   * entity relation style. Default is ENTITY_SEGMENT. Value is "segment".
   */
  STYLE_SEGMENT: 'segment',

  /**
   * Variable: STYLE_ENDARROW
   *
   * Defines the key for the end arrow marker. Possible values are all
   * constants with an ARROW-prefix. This is only used in <mxConnector>.
   * Value is "endArrow".
   *
   * Example:
   * (code)
   * style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
   * (end)
   */
  STYLE_ENDARROW: 'endArrow',

  /**
   * Variable: STYLE_STARTARROW
   *
   * Defines the key for the start arrow marker. Possible values are all
   * constants with an ARROW-prefix. This is only used in <mxConnector>.
   * See <STYLE_ENDARROW>. Value is "startArrow".
   */
  STYLE_STARTARROW: 'startArrow',

  /**
   * Variable: STYLE_ENDSIZE
   *
   * Defines the key for the endSize style. The type of this value is numeric
   * and the value represents the size of the end marker in pixels. Value is
   * "endSize".
   */
  STYLE_ENDSIZE: 'endSize',

  /**
   * Variable: STYLE_STARTSIZE
   *
   * Defines the key for the startSize style. The type of this value is
   * numeric and the value represents the size of the start marker or the
   * size of the swimlane title region depending on the shape it is used for.
   * Value is "startSize".
   */
  STYLE_STARTSIZE: 'startSize',

  /**
   * Variable: STYLE_SWIMLANE_LINE
   *
   * Defines the key for the swimlaneLine style. This style specifies whether
   * the line between the title regio of a swimlane should be visible. Use 0
   * for hidden or 1 (default) for visible. Value is "swimlaneLine".
   */
  STYLE_SWIMLANE_LINE: 'swimlaneLine',

  /**
   * Variable: STYLE_ENDFILL
   *
   * Defines the key for the endFill style. Use 0 for no fill or 1 (default)
   * for fill. (This style is only exported via <mxImageExport>.) Value is
   * "endFill".
   */
  STYLE_ENDFILL: 'endFill',

  /**
   * Variable: STYLE_STARTFILL
   *
   * Defines the key for the startFill style. Use 0 for no fill or 1 (default)
   * for fill. (This style is only exported via <mxImageExport>.) Value is
   * "startFill".
   */
  STYLE_STARTFILL: 'startFill',

  /**
   * Variable: STYLE_DASHED
   *
   * Defines the key for the dashed style. Use 0 (default) for non-dashed or 1
   * for dashed. Value is "dashed".
   */
  STYLE_DASHED: 'dashed',

  /**
   * Variable: STYLE_DASH_PATTERN
   *
   * Defines the key for the dashed pattern style in SVG and image exports.
   * The type of this value is a space separated list of numbers that specify
   * a custom-defined dash pattern. Dash styles are defined in terms of the
   * length of the dash (the drawn part of the stroke) and the length of the
   * space between the dashes. The lengths are relative to the line width: a
   * length of "1" is equal to the line width. VML ignores this style and
   * uses dashStyle instead as defined in the VML specification. This style
   * is only used in the <mxConnector> shape. Value is "dashPattern".
   */
  STYLE_DASH_PATTERN: 'dashPattern',

  /**
   * Variable: STYLE_FIX_DASH
   *
   * Defines the key for the fixDash style. Use 0 (default) for dash patterns
   * that depend on the linewidth and 1 for dash patterns that ignore the
   * line width. Value is "fixDash".
   */
  STYLE_FIX_DASH: 'fixDash',

  /**
   * Variable: STYLE_ROUNDED
   *
   * Defines the key for the rounded style. The type of this value is
   * Boolean. For edges this determines whether or not joins between edges
   * segments are smoothed to a rounded finish. For vertices that have the
   * rectangle shape, this determines whether or not the rectangle is
   * rounded. Use 0 (default) for non-rounded or 1 for rounded. Value is
   * "rounded".
   */
  STYLE_ROUNDED: 'rounded',

  /**
   * Variable: STYLE_CURVED
   *
   * Defines the key for the curved style. The type of this value is
   * Boolean. It is only applicable for connector shapes. Use 0 (default)
   * for non-curved or 1 for curved. Value is "curved".
   */
  STYLE_CURVED: 'curved',

  /**
   * Variable: STYLE_ARCSIZE
   *
   * Defines the rounding factor for a rounded rectangle in percent (without
   * the percent sign). Possible values are between 0 and 100. If this value
   * is not specified then RECTANGLE_ROUNDING_FACTOR * 100 is used. For
   * edges, this defines the absolute size of rounded corners in pixels. If
   * this values is not specified then LINE_ARCSIZE is used.
   * (This style is only exported via <mxImageExport>.) Value is "arcSize".
   */
  STYLE_ARCSIZE: 'arcSize',

  /**
   * Variable: STYLE_ABSOLUTE_ARCSIZE
   *
   * Defines the key for the absolute arc size style. This specifies if
   * arcSize for rectangles is abolute or relative. Possible values are 1
   * and 0 (default). Value is "absoluteArcSize".
   */
  STYLE_ABSOLUTE_ARCSIZE: 'absoluteArcSize',

  /**
   * Variable: STYLE_SOURCE_PERIMETER_SPACING
   *
   * Defines the key for the source perimeter spacing. The type of this value
   * is numeric. This is the distance between the source connection point of
   * an edge and the perimeter of the source vertex in pixels. This style
   * only applies to edges. Value is "sourcePerimeterSpacing".
   */
  STYLE_SOURCE_PERIMETER_SPACING: 'sourcePerimeterSpacing',

  /**
   * Variable: STYLE_TARGET_PERIMETER_SPACING
   *
   * Defines the key for the target perimeter spacing. The type of this value
   * is numeric. This is the distance between the target connection point of
   * an edge and the perimeter of the target vertex in pixels. This style
   * only applies to edges. Value is "targetPerimeterSpacing".
   */
  STYLE_TARGET_PERIMETER_SPACING: 'targetPerimeterSpacing',

  /**
   * Variable: STYLE_PERIMETER_SPACING
   *
   * Defines the key for the perimeter spacing. This is the distance between
   * the connection point and the perimeter in pixels. When used in a vertex
   * style, this applies to all incoming edges to floating ports (edges that
   * terminate on the perimeter of the vertex). When used in an edge style,
   * this spacing applies to the source and target separately, if they
   * terminate in floating ports (on the perimeter of the vertex). Value is
   * "perimeterSpacing".
   */
  STYLE_PERIMETER_SPACING: 'perimeterSpacing',

  /**
   * Variable: STYLE_SPACING
   *
   * Defines the key for the spacing. The value represents the spacing, in
   * pixels, added to each side of a label in a vertex (style applies to
   * vertices only). Value is "spacing".
   */
  STYLE_SPACING: 'spacing',

  /**
   * Variable: STYLE_SPACING_TOP
   *
   * Defines the key for the spacingTop style. The value represents the
   * spacing, in pixels, added to the top side of a label in a vertex (style
   * applies to vertices only). Value is "spacingTop".
   */
  STYLE_SPACING_TOP: 'spacingTop',

  /**
   * Variable: STYLE_SPACING_LEFT
   *
   * Defines the key for the spacingLeft style. The value represents the
   * spacing, in pixels, added to the left side of a label in a vertex (style
   * applies to vertices only). Value is "spacingLeft".
   */
  STYLE_SPACING_LEFT: 'spacingLeft',

  /**
   * Variable: STYLE_SPACING_BOTTOM
   *
   * Defines the key for the spacingBottom style The value represents the
   * spacing, in pixels, added to the bottom side of a label in a vertex
   * (style applies to vertices only). Value is "spacingBottom".
   */
  STYLE_SPACING_BOTTOM: 'spacingBottom',

  /**
   * Variable: STYLE_SPACING_RIGHT
   *
   * Defines the key for the spacingRight style The value represents the
   * spacing, in pixels, added to the right side of a label in a vertex (style
   * applies to vertices only). Value is "spacingRight".
   */
  STYLE_SPACING_RIGHT: 'spacingRight',

  /**
   * Variable: STYLE_HORIZONTAL
   *
   * Defines the key for the horizontal style. Possible values are
   * true or false. This value only applies to vertices. If the <STYLE_SHAPE>
   * is "SHAPE_SWIMLANE" a value of false indicates that the
   * swimlane should be drawn vertically, true indicates to draw it
   * horizontally. If the shape style does not indicate that this vertex is a
   * swimlane, this value affects only whether the label is drawn
   * horizontally or vertically. Value is "horizontal".
   */
  STYLE_HORIZONTAL: 'horizontal',

  /**
   * Variable: STYLE_DIRECTION
   *
   * Defines the key for the direction style. The direction style is used
   * to specify the direction of certain shapes (eg. <mxTriangle>).
   * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
   * <DIRECTION_NORTH> and <DIRECTION_SOUTH>. Value is "direction".
   */
  STYLE_DIRECTION: 'direction',

  /**
   * Variable: STYLE_ANCHOR_POINT_DIRECTION
   *
   * Defines the key for the anchorPointDirection style. The defines if the
   * direction style should be taken into account when computing the fixed
   * point location for connected edges. Default is 1 (yes). Set this to 0
   * to ignore the direction style for fixed connection points. Value is
   * "anchorPointDirection".
   */
  STYLE_ANCHOR_POINT_DIRECTION: 'anchorPointDirection',

  /**
   * Variable: STYLE_ELBOW
   *
   * Defines the key for the elbow style. Possible values are
   * <ELBOW_HORIZONTAL> and <ELBOW_VERTICAL>. Default is <ELBOW_HORIZONTAL>.
   * This defines how the three segment orthogonal edge style leaves its
   * terminal vertices. The vertical style leaves the terminal vertices at
   * the top and bottom sides. Value is "elbow".
   */
  STYLE_ELBOW: 'elbow',

  /**
   * Variable: STYLE_FONTCOLOR
   *
   * Defines the key for the fontColor style. Possible values are all HTML
   * color names or HEX codes. Value is "fontColor".
   */
  STYLE_FONTCOLOR: 'fontColor',

  /**
   * Variable: STYLE_FONTFAMILY
   *
   * Defines the key for the fontFamily style. Possible values are names such
   * as Arial; Dialog; Verdana; Times New Roman. The value is of type String.
   * Value is fontFamily.
   */
  STYLE_FONTFAMILY: 'fontFamily',

  /**
   * Variable: STYLE_FONTSIZE
   *
   * Defines the key for the fontSize style (in px). The type of the value
   * is int. Value is "fontSize".
   */
  STYLE_FONTSIZE: 'fontSize',

  /**
   * Variable: STYLE_FONTSTYLE
   *
   * Defines the key for the fontStyle style. Values may be any logical AND
   * (sum) of <FONT_BOLD>, <FONT_ITALIC> and <FONT_UNDERLINE>.
   * The type of the value is int. Value is "fontStyle".
   */
  STYLE_FONTSTYLE: 'fontStyle',

  /**
   * Variable: STYLE_ASPECT
   *
   * Defines the key for the aspect style. Possible values are empty or fixed.
   * If fixed is used then the aspect ratio of the cell will be maintained
   * when resizing. Default is empty. Value is "aspect".
   */
  STYLE_ASPECT: 'aspect',

  /**
   * Variable: STYLE_AUTOSIZE
   *
   * Defines the key for the autosize style. This specifies if a cell should be
   * resized automatically if the value has changed. Possible values are 0 or 1.
   * Default is 0. See <mxGraph.isAutoSizeCell>. This is normally combined with
   * <STYLE_RESIZABLE> to disable manual sizing. Value is "autosize".
   */
  STYLE_AUTOSIZE: 'autosize',

  /**
   * Variable: STYLE_FOLDABLE
   *
   * Defines the key for the foldable style. This specifies if a cell is foldable
   * using a folding icon. Possible values are 0 or 1. Default is 1. See
   * <mxGraph.isCellFoldable>. Value is "foldable".
   */
  STYLE_FOLDABLE: 'foldable',

  /**
   * Variable: STYLE_EDITABLE
   *
   * Defines the key for the editable style. This specifies if the value of
   * a cell can be edited using the in-place editor. Possible values are 0 or
   * 1. Default is 1. See <mxGraph.isCellEditable>. Value is "editable".
   */
  STYLE_EDITABLE: 'editable',

  /**
   * Variable: STYLE_BACKGROUND_OUTLINE
   *
   * Defines the key for the backgroundOutline style. This specifies if a
   * only the background of a cell should be painted when it is highlighted.
   * Possible values are 0 or 1. Default is 0. Value is "backgroundOutline".
   */
  STYLE_BACKGROUND_OUTLINE: 'backgroundOutline',

  /**
   * Variable: STYLE_BENDABLE
   *
   * Defines the key for the bendable style. This specifies if the control
   * points of an edge can be moved. Possible values are 0 or 1. Default is
   * 1. See <mxGraph.isCellBendable>. Value is "bendable".
   */
  STYLE_BENDABLE: 'bendable',

  /**
   * Variable: STYLE_MOVABLE
   *
   * Defines the key for the movable style. This specifies if a cell can
   * be moved. Possible values are 0 or 1. Default is 1. See
   * <mxGraph.isCellMovable>. Value is "movable".
   */
  STYLE_MOVABLE: 'movable',

  /**
   * Variable: STYLE_RESIZABLE
   *
   * Defines the key for the resizable style. This specifies if a cell can
   * be resized. Possible values are 0 or 1. Default is 1. See
   * <mxGraph.isCellResizable>. Value is "resizable".
   */
  STYLE_RESIZABLE: 'resizable',

  /**
   * Variable: STYLE_RESIZE_WIDTH
   *
   * Defines the key for the resizeWidth style. This specifies if a cell's
   * width is resized if the parent is resized. If this is 1 then the width
   * will be resized even if the cell's geometry is relative. If this is 0
   * then the cell's width will not be resized. Default is not defined. Value
   * is "resizeWidth".
   */
  STYLE_RESIZE_WIDTH: 'resizeWidth',

  /**
   * Variable: STYLE_RESIZE_WIDTH
   *
   * Defines the key for the resizeHeight style. This specifies if a cell's
   * height if resize if the parent is resized. If this is 1 then the height
   * will be resized even if the cell's geometry is relative. If this is 0
   * then the cell's height will not be resized. Default is not defined. Value
   * is "resizeHeight".
   */
  STYLE_RESIZE_HEIGHT: 'resizeHeight',

  /**
   * Variable: STYLE_ROTATABLE
   *
   * Defines the key for the rotatable style. This specifies if a cell can
   * be rotated. Possible values are 0 or 1. Default is 1. See
   * <mxGraph.isCellRotatable>. Value is "rotatable".
   */
  STYLE_ROTATABLE: 'rotatable',

  /**
   * Variable: STYLE_CLONEABLE
   *
   * Defines the key for the cloneable style. This specifies if a cell can
   * be cloned. Possible values are 0 or 1. Default is 1. See
   * <mxGraph.isCellCloneable>. Value is "cloneable".
   */
  STYLE_CLONEABLE: 'cloneable',

  /**
   * Variable: STYLE_DELETABLE
   *
   * Defines the key for the deletable style. This specifies if a cell can be
   * deleted. Possible values are 0 or 1. Default is 1. See
   * <mxGraph.isCellDeletable>. Value is "deletable".
   */
  STYLE_DELETABLE: 'deletable',

  /**
   * Variable: STYLE_SHAPE
   *
   * Defines the key for the shape. Possible values are all constants with
   * a SHAPE-prefix or any newly defined shape names. Value is "shape".
   */
  STYLE_SHAPE: 'shape',

  /**
   * Variable: STYLE_EDGE
   *
   * Defines the key for the edge style. Possible values are the functions
   * defined in <mxEdgeStyle>. Value is "edgeStyle".
   */
  STYLE_EDGE: 'edgeStyle',

  /**
   * Variable: STYLE_JETTY_SIZE
   *
   * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
   * Default is 10. Possible values are all numeric values or "auto".
   * Jetty size is the minimum length of the orthogonal segment before
   * it attaches to a shape.
   * Value is "jettySize".
   */
  STYLE_JETTY_SIZE: 'jettySize',

  /**
   * Variable: STYLE_SOURCE_JETTY_SIZE
   *
   * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
   * Default is 10. Possible values are numeric values or "auto". This has
   * precedence over <STYLE_JETTY_SIZE>. Value is "sourceJettySize".
   */
  STYLE_SOURCE_JETTY_SIZE: 'sourceJettySize',

  /**
   * Variable: targetJettySize
   *
   * Defines the key for the jetty size in <mxEdgeStyle.OrthConnector>.
   * Default is 10. Possible values are numeric values or "auto". This has
   * precedence over <STYLE_JETTY_SIZE>. Value is "targetJettySize".
   */
  STYLE_TARGET_JETTY_SIZE: 'targetJettySize',

  /**
   * Variable: STYLE_LOOP
   *
   * Defines the key for the loop style. Possible values are the functions
   * defined in <mxEdgeStyle>. Value is "loopStyle". Default is
   * <mxGraph.defaultLoopStylean>.
   */
  STYLE_LOOP: 'loopStyle',

  /**
   * Variable: STYLE_ORTHOGONAL_LOOP
   *
   * Defines the key for the orthogonal loop style. Possible values are 0 and
   * 1. Default is 0. Value is "orthogonalLoop". Use this style to specify
   * if loops with no waypoints and defined anchor points should be routed
   * using <STYLE_LOOP> or not routed.
   */
  STYLE_ORTHOGONAL_LOOP: 'orthogonalLoop',

  /**
   * Variable: STYLE_ROUTING_CENTER_X
   *
   * Defines the key for the horizontal routing center. Possible values are
   * between -0.5 and 0.5. This is the relative offset from the center used
   * for connecting edges. The type of this value is numeric. Value is
   * "routingCenterX".
   */
  STYLE_ROUTING_CENTER_X: 'routingCenterX',

  /**
   * Variable: STYLE_ROUTING_CENTER_Y
   *
   * Defines the key for the vertical routing center. Possible values are
   * between -0.5 and 0.5. This is the relative offset from the center used
   * for connecting edges. The type of this value is numeric. Value is
   * "routingCenterY".
   */
  STYLE_ROUTING_CENTER_Y: 'routingCenterY',

  /**
   * Variable: FONT_BOLD
   *
   * Constant for bold fonts. Default is 1.
   */
  FONT_BOLD: 1,

  /**
   * Variable: FONT_ITALIC
   *
   * Constant for italic fonts. Default is 2.
   */
  FONT_ITALIC: 2,

  /**
   * Variable: FONT_UNDERLINE
   *
   * Constant for underlined fonts. Default is 4.
   */
  FONT_UNDERLINE: 4,

  /**
   * Variable: FONT_STRIKETHROUGH
   *
   * Constant for strikthrough fonts. Default is 8.
   */
  FONT_STRIKETHROUGH: 8,

  /**
   * Variable: SHAPE_RECTANGLE
   *
   * Name under which <mxRectangleShape> is registered in <mxCellRenderer>.
   * Default is rectangle.
   */
  SHAPE_RECTANGLE: 'rectangle',

  /**
   * Variable: SHAPE_ELLIPSE
   *
   * Name under which <mxEllipse> is registered in <mxCellRenderer>.
   * Default is ellipse.
   */
  SHAPE_ELLIPSE: 'ellipse',

  /**
   * Variable: SHAPE_DOUBLE_ELLIPSE
   *
   * Name under which <mxDoubleEllipse> is registered in <mxCellRenderer>.
   * Default is doubleEllipse.
   */
  SHAPE_DOUBLE_ELLIPSE: 'doubleEllipse',

  /**
   * Variable: SHAPE_RHOMBUS
   *
   * Name under which <mxRhombus> is registered in <mxCellRenderer>.
   * Default is rhombus.
   */
  SHAPE_RHOMBUS: 'rhombus',

  /**
   * Variable: SHAPE_LINE
   *
   * Name under which <mxLine> is registered in <mxCellRenderer>.
   * Default is line.
   */
  SHAPE_LINE: 'line',

  /**
   * Variable: SHAPE_IMAGE
   *
   * Name under which <mxImageShape> is registered in <mxCellRenderer>.
   * Default is image.
   */
  SHAPE_IMAGE: 'image',

  /**
   * Variable: SHAPE_ARROW
   *
   * Name under which <mxArrow> is registered in <mxCellRenderer>.
   * Default is arrow.
   */
  SHAPE_ARROW: 'arrow',

  /**
   * Variable: SHAPE_ARROW_CONNECTOR
   *
   * Name under which <mxArrowConnector> is registered in <mxCellRenderer>.
   * Default is arrowConnector.
   */
  SHAPE_ARROW_CONNECTOR: 'arrowConnector',

  /**
   * Variable: SHAPE_LABEL
   *
   * Name under which <mxLabel> is registered in <mxCellRenderer>.
   * Default is label.
   */
  SHAPE_LABEL: 'label',

  /**
   * Variable: SHAPE_CYLINDER
   *
   * Name under which <mxCylinder> is registered in <mxCellRenderer>.
   * Default is cylinder.
   */
  SHAPE_CYLINDER: 'cylinder',

  /**
   * Variable: SHAPE_SWIMLANE
   *
   * Name under which <mxSwimlane> is registered in <mxCellRenderer>.
   * Default is swimlane.
   */
  SHAPE_SWIMLANE: 'swimlane',

  /**
   * Variable: SHAPE_CONNECTOR
   *
   * Name under which <mxConnector> is registered in <mxCellRenderer>.
   * Default is connector.
   */
  SHAPE_CONNECTOR: 'connector',

  /**
   * Variable: SHAPE_ACTOR
   *
   * Name under which <mxActor> is registered in <mxCellRenderer>.
   * Default is actor.
   */
  SHAPE_ACTOR: 'actor',

  /**
   * Variable: SHAPE_CLOUD
   *
   * Name under which <mxCloud> is registered in <mxCellRenderer>.
   * Default is cloud.
   */
  SHAPE_CLOUD: 'cloud',

  /**
   * Variable: SHAPE_TRIANGLE
   *
   * Name under which <mxTriangle> is registered in <mxCellRenderer>.
   * Default is triangle.
   */
  SHAPE_TRIANGLE: 'triangle',

  /**
   * Variable: SHAPE_HEXAGON
   *
   * Name under which <mxHexagon> is registered in <mxCellRenderer>.
   * Default is hexagon.
   */
  SHAPE_HEXAGON: 'hexagon',

  /**
   * Variable: ARROW_CLASSIC
   *
   * Constant for classic arrow markers.
   */
  ARROW_CLASSIC: 'classic',

  /**
   * Variable: ARROW_CLASSIC_THIN
   *
   * Constant for thin classic arrow markers.
   */
  ARROW_CLASSIC_THIN: 'classicThin',

  /**
   * Variable: ARROW_BLOCK
   *
   * Constant for block arrow markers.
   */
  ARROW_BLOCK: 'block',

  /**
   * Variable: ARROW_BLOCK_THIN
   *
   * Constant for thin block arrow markers.
   */
  ARROW_BLOCK_THIN: 'blockThin',

  /**
   * Variable: ARROW_OPEN
   *
   * Constant for open arrow markers.
   */
  ARROW_OPEN: 'open',

  /**
   * Variable: ARROW_OPEN_THIN
   *
   * Constant for thin open arrow markers.
   */
  ARROW_OPEN_THIN: 'openThin',

  /**
   * Variable: ARROW_OVAL
   *
   * Constant for oval arrow markers.
   */
  ARROW_OVAL: 'oval',

  /**
   * Variable: ARROW_DIAMOND
   *
   * Constant for diamond arrow markers.
   */
  ARROW_DIAMOND: 'diamond',

  /**
   * Variable: ARROW_DIAMOND_THIN
   *
   * Constant for thin diamond arrow markers.
   */
  ARROW_DIAMOND_THIN: 'diamondThin',

  /**
   * Variable: ALIGN_LEFT
   *
   * Constant for left horizontal alignment. Default is left.
   */
  ALIGN_LEFT: 'left',

  /**
   * Variable: ALIGN_CENTER
   *
   * Constant for center horizontal alignment. Default is center.
   */
  ALIGN_CENTER: 'center',

  /**
   * Variable: ALIGN_RIGHT
   *
   * Constant for right horizontal alignment. Default is right.
   */
  ALIGN_RIGHT: 'right',

  /**
   * Variable: ALIGN_TOP
   *
   * Constant for top vertical alignment. Default is top.
   */
  ALIGN_TOP: 'top',

  /**
   * Variable: ALIGN_MIDDLE
   *
   * Constant for middle vertical alignment. Default is middle.
   */
  ALIGN_MIDDLE: 'middle',

  /**
   * Variable: ALIGN_BOTTOM
   *
   * Constant for bottom vertical alignment. Default is bottom.
   */
  ALIGN_BOTTOM: 'bottom',

  /**
   * Variable: DIRECTION_NORTH
   *
   * Constant for direction north. Default is north.
   */
  DIRECTION_NORTH: 'north',

  /**
   * Variable: DIRECTION_SOUTH
   *
   * Constant for direction south. Default is south.
   */
  DIRECTION_SOUTH: 'south',

  /**
   * Variable: DIRECTION_EAST
   *
   * Constant for direction east. Default is east.
   */
  DIRECTION_EAST: 'east',

  /**
   * Variable: DIRECTION_WEST
   *
   * Constant for direction west. Default is west.
   */
  DIRECTION_WEST: 'west',

  /**
   * Variable: TEXT_DIRECTION_DEFAULT
   *
   * Constant for text direction default. Default is an empty string. Use
   * this value to use the default text direction of the operating system.
   */
  TEXT_DIRECTION_DEFAULT: '',

  /**
   * Variable: TEXT_DIRECTION_AUTO
   *
   * Constant for text direction automatic. Default is auto. Use this value
   * to find the direction for a given text with <mxText.getAutoDirection>.
   */
  TEXT_DIRECTION_AUTO: 'auto',

  /**
   * Variable: TEXT_DIRECTION_LTR
   *
   * Constant for text direction left to right. Default is ltr. Use this
   * value for left to right text direction.
   */
  TEXT_DIRECTION_LTR: 'ltr',

  /**
   * Variable: TEXT_DIRECTION_RTL
   *
   * Constant for text direction right to left. Default is rtl. Use this
   * value for right to left text direction.
   */
  TEXT_DIRECTION_RTL: 'rtl',

  /**
   * Variable: DIRECTION_MASK_NONE
   *
   * Constant for no direction.
   */
  DIRECTION_MASK_NONE: 0,

  /**
   * Variable: DIRECTION_MASK_WEST
   *
   * Bitwise mask for west direction.
   */
  DIRECTION_MASK_WEST: 1,

  /**
   * Variable: DIRECTION_MASK_NORTH
   *
   * Bitwise mask for north direction.
   */
  DIRECTION_MASK_NORTH: 2,

  /**
   * Variable: DIRECTION_MASK_SOUTH
   *
   * Bitwise mask for south direction.
   */
  DIRECTION_MASK_SOUTH: 4,

  /**
   * Variable: DIRECTION_MASK_EAST
   *
   * Bitwise mask for east direction.
   */
  DIRECTION_MASK_EAST: 8,

  /**
   * Variable: DIRECTION_MASK_ALL
   *
   * Bitwise mask for all directions.
   */
  DIRECTION_MASK_ALL: 15,

  /**
   * Variable: ELBOW_VERTICAL
   *
   * Constant for elbow vertical. Default is horizontal.
   */
  ELBOW_VERTICAL: 'vertical',

  /**
   * Variable: ELBOW_HORIZONTAL
   *
   * Constant for elbow horizontal. Default is horizontal.
   */
  ELBOW_HORIZONTAL: 'horizontal',

  /**
   * Variable: EDGESTYLE_ELBOW
   *
   * Name of the elbow edge style. Can be used as a string value
   * for the STYLE_EDGE style.
   */
  EDGESTYLE_ELBOW: 'elbowEdgeStyle',

  /**
   * Variable: EDGESTYLE_ENTITY_RELATION
   *
   * Name of the entity relation edge style. Can be used as a string value
   * for the STYLE_EDGE style.
   */
  EDGESTYLE_ENTITY_RELATION: 'entityRelationEdgeStyle',

  /**
   * Variable: EDGESTYLE_LOOP
   *
   * Name of the loop edge style. Can be used as a string value
   * for the STYLE_EDGE style.
   */
  EDGESTYLE_LOOP: 'loopEdgeStyle',

  /**
   * Variable: EDGESTYLE_SIDETOSIDE
   *
   * Name of the side to side edge style. Can be used as a string value
   * for the STYLE_EDGE style.
   */
  EDGESTYLE_SIDETOSIDE: 'sideToSideEdgeStyle',

  /**
   * Variable: EDGESTYLE_TOPTOBOTTOM
   *
   * Name of the top to bottom edge style. Can be used as a string value
   * for the STYLE_EDGE style.
   */
  EDGESTYLE_TOPTOBOTTOM: 'topToBottomEdgeStyle',

  /**
   * Variable: EDGESTYLE_ORTHOGONAL
   *
   * Name of the generic orthogonal edge style. Can be used as a string value
   * for the STYLE_EDGE style.
   */
  EDGESTYLE_ORTHOGONAL: 'orthogonalEdgeStyle',

  /**
   * Variable: EDGESTYLE_SEGMENT
   *
   * Name of the generic segment edge style. Can be used as a string value
   * for the STYLE_EDGE style.
   */
  EDGESTYLE_SEGMENT: 'segmentEdgeStyle',

  /**
   * Variable: PERIMETER_ELLIPSE
   *
   * Name of the ellipse perimeter. Can be used as a string value
   * for the STYLE_PERIMETER style.
   */
  PERIMETER_ELLIPSE: 'ellipsePerimeter',

  /**
   * Variable: PERIMETER_RECTANGLE
   *
   * Name of the rectangle perimeter. Can be used as a string value
   * for the STYLE_PERIMETER style.
   */
  PERIMETER_RECTANGLE: 'rectanglePerimeter',

  /**
   * Variable: PERIMETER_RHOMBUS
   *
   * Name of the rhombus perimeter. Can be used as a string value
   * for the STYLE_PERIMETER style.
   */
  PERIMETER_RHOMBUS: 'rhombusPerimeter',

  /**
   * Variable: PERIMETER_HEXAGON
   *
   * Name of the hexagon perimeter. Can be used as a string value
   * for the STYLE_PERIMETER style.
   */
  PERIMETER_HEXAGON: 'hexagonPerimeter',

  /**
   * Variable: PERIMETER_TRIANGLE
   *
   * Name of the triangle perimeter. Can be used as a string value
   * for the STYLE_PERIMETER style.
   */
  PERIMETER_TRIANGLE: 'trianglePerimeter',
};

/**
 * util/mxEventObject.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxEventObject
 *
 * The mxEventObject is a wrapper for all properties of a single event.
 * Additionally, it also offers functions to consume the event and check if it
 * was consumed as follows:
 *
 * (code)
 * evt.consume();
 * INV: evt.isConsumed() == true
 * (end)
 *
 * Constructor: mxEventObject
 *
 * Constructs a new event object with the specified name. An optional
 * sequence of key, value pairs can be appended to define properties.
 *
 * Example:
 *
 * (code)
 * new mxEventObject("eventName", key1, val1, .., keyN, valN)
 * (end)
 */
function mxEventObject(name) {
  this.name = name;
  this.properties = [];

  for (var i = 1; i < arguments.length; i += 2) {
    if (arguments[i + 1] != null) {
      this.properties[arguments[i]] = arguments[i + 1];
    }
  }
}

/**
 * Variable: name
 *
 * Holds the name.
 */
mxEventObject.prototype.name = null;

/**
 * Variable: properties
 *
 * Holds the properties as an associative array.
 */
mxEventObject.prototype.properties = null;

/**
 * Variable: consumed
 *
 * Holds the consumed state. Default is false.
 */
mxEventObject.prototype.consumed = false;

/**
 * Function: getName
 *
 * Returns <name>.
 */
mxEventObject.prototype.getName = function () {
  return this.name;
};

/**
 * Function: getProperties
 *
 * Returns <properties>.
 */
mxEventObject.prototype.getProperties = function () {
  return this.properties;
};

/**
 * Function: getProperty
 *
 * Returns the property for the given key.
 */
mxEventObject.prototype.getProperty = function (key) {
  return this.properties[key];
};

/**
 * Function: isConsumed
 *
 * Returns true if the event has been consumed.
 */
mxEventObject.prototype.isConsumed = function () {
  return this.consumed;
};

/**
 * Function: consume
 *
 * Consumes the event.
 */
mxEventObject.prototype.consume = function () {
  this.consumed = true;
};

/**
 * util/mxMouseEvent.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxMouseEvent
 *
 * Base class for all mouse events in mxGraph. A listener for this event should
 * implement the following methods:
 *
 * (code)
 * graph.addMouseListener(
 * {
 *   mouseDown: function(sender, evt)
 *   {
 *     mxLog.debug('mouseDown');
 *   },
 *   mouseMove: function(sender, evt)
 *   {
 *     mxLog.debug('mouseMove');
 *   },
 *   mouseUp: function(sender, evt)
 *   {
 *     mxLog.debug('mouseUp');
 *   }
 * });
 * (end)
 *
 * Constructor: mxMouseEvent
 *
 * Constructs a new event object for the given arguments.
 *
 * Parameters:
 *
 * evt - Native mouse event.
 * state - Optional <mxCellState> under the mouse.
 *
 */
function mxMouseEvent(evt, state) {
  this.evt = evt;
  this.state = state;
  this.sourceState = state;
}

/**
 * Variable: consumed
 *
 * Holds the consumed state of this event.
 */
mxMouseEvent.prototype.consumed = false;

/**
 * Variable: evt
 *
 * Holds the inner event object.
 */
mxMouseEvent.prototype.evt = null;

/**
 * Variable: graphX
 *
 * Holds the x-coordinate of the event in the graph. This value is set in
 * <mxGraph.fireMouseEvent>.
 */
mxMouseEvent.prototype.graphX = null;

/**
 * Variable: graphY
 *
 * Holds the y-coordinate of the event in the graph. This value is set in
 * <mxGraph.fireMouseEvent>.
 */
mxMouseEvent.prototype.graphY = null;

/**
 * Variable: state
 *
 * Holds the optional <mxCellState> associated with this event.
 */
mxMouseEvent.prototype.state = null;

/**
 * Variable: sourceState
 *
 * Holds the <mxCellState> that was passed to the constructor. This can be
 * different from <state> depending on the result of <mxGraph.getEventState>.
 */
mxMouseEvent.prototype.sourceState = null;

/**
 * Function: getEvent
 *
 * Returns <evt>.
 */
mxMouseEvent.prototype.getEvent = function () {
  return this.evt;
};

/**
 * Function: getSource
 *
 * Returns the target DOM element using <mxEvent.getSource> for <evt>.
 */
mxMouseEvent.prototype.getSource = function () {
  return mxEvent.getSource(this.evt);
};

/**
 * Function: isSource
 *
 * Returns true if the given <mxShape> is the source of <evt>.
 */
mxMouseEvent.prototype.isSource = function (shape) {
  if (shape != null) {
    return mxUtils.isAncestorNode(shape.node, this.getSource());
  }

  return false;
};

/**
 * Function: getX
 *
 * Returns <evt.clientX>.
 */
mxMouseEvent.prototype.getX = function () {
  return mxEvent.getClientX(this.getEvent());
};

/**
 * Function: getY
 *
 * Returns <evt.clientY>.
 */
mxMouseEvent.prototype.getY = function () {
  return mxEvent.getClientY(this.getEvent());
};

/**
 * Function: getGraphX
 *
 * Returns <graphX>.
 */
mxMouseEvent.prototype.getGraphX = function () {
  return this.graphX;
};

/**
 * Function: getGraphY
 *
 * Returns <graphY>.
 */
mxMouseEvent.prototype.getGraphY = function () {
  return this.graphY;
};

/**
 * Function: getState
 *
 * Returns <state>.
 */
mxMouseEvent.prototype.getState = function () {
  return this.state;
};

/**
 * Function: getCell
 *
 * Returns the <mxCell> in <state> is not null.
 */
mxMouseEvent.prototype.getCell = function () {
  var state = this.getState();

  if (state != null) {
    return state.cell;
  }

  return null;
};

/**
 * Function: isPopupTrigger
 *
 * Returns true if the event is a popup trigger.
 */
mxMouseEvent.prototype.isPopupTrigger = function () {
  return mxEvent.isPopupTrigger(this.getEvent());
};

/**
 * Function: isConsumed
 *
 * Returns <consumed>.
 */
mxMouseEvent.prototype.isConsumed = function () {
  return this.consumed;
};

/**
 * Function: consume
 *
 * Sets <consumed> to true and invokes preventDefault on the native event
 * if such a method is defined. This is used mainly to avoid the cursor from
 * being changed to a text cursor in Webkit. You can use the preventDefault
 * flag to disable this functionality.
 *
 * Parameters:
 *
 * preventDefault - Specifies if the native event should be canceled. Default
 * is true.
 */
mxMouseEvent.prototype.consume = function (preventDefault) {
  preventDefault = preventDefault != null ? preventDefault : this.evt.touches != null || mxEvent.isMouseEvent(this.evt);

  if (preventDefault && this.evt.preventDefault) {
    this.evt.preventDefault();
  }

  // Workaround for images being dragged in IE
  // Does not change returnValue in Opera
  if (mxClient.IS_IE) {
    this.evt.returnValue = true;
  }

  // Sets local consumed state
  this.consumed = true;
};

/**
 * util/mxEventSource.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxEventSource
 *
 * Base class for objects that dispatch named events. To create a subclass that
 * inherits from mxEventSource, the following code is used.
 *
 * (code)
 * function MyClass() { };
 *
 * MyClass.prototype = new mxEventSource();
 * MyClass.prototype.constructor = MyClass;
 * (end)
 *
 * Known Subclasses:
 *
 * <mxGraphModel>, <mxGraph>, <mxGraphView>, <mxEditor>, <mxCellOverlay>,
 * <mxToolbar>, <mxWindow>
 *
 * Constructor: mxEventSource
 *
 * Constructs a new event source.
 */
function mxEventSource(eventSource) {
  this.setEventSource(eventSource);
}

/**
 * Variable: eventListeners
 *
 * Holds the event names and associated listeners in an array. The array
 * contains the event name followed by the respective listener for each
 * registered listener.
 */
mxEventSource.prototype.eventListeners = null;

/**
 * Variable: eventsEnabled
 *
 * Specifies if events can be fired. Default is true.
 */
mxEventSource.prototype.eventsEnabled = true;

/**
 * Variable: eventSource
 *
 * Optional source for events. Default is null.
 */
mxEventSource.prototype.eventSource = null;

/**
 * Function: isEventsEnabled
 *
 * Returns <eventsEnabled>.
 */
mxEventSource.prototype.isEventsEnabled = function () {
  return this.eventsEnabled;
};

/**
 * Function: setEventsEnabled
 *
 * Sets <eventsEnabled>.
 */
mxEventSource.prototype.setEventsEnabled = function (value) {
  this.eventsEnabled = value;
};

/**
 * Function: getEventSource
 *
 * Returns <eventSource>.
 */
mxEventSource.prototype.getEventSource = function () {
  return this.eventSource;
};

/**
 * Function: setEventSource
 *
 * Sets <eventSource>.
 */
mxEventSource.prototype.setEventSource = function (value) {
  this.eventSource = value;
};

/**
 * Function: addListener
 *
 * Binds the specified function to the given event name. If no event name
 * is given, then the listener is registered for all events.
 *
 * The parameters of the listener are the sender and an <mxEventObject>.
 */
mxEventSource.prototype.addListener = function (name, funct) {
  if (this.eventListeners == null) {
    this.eventListeners = [];
  }

  this.eventListeners.push(name);
  this.eventListeners.push(funct);
};

/**
 * Function: removeListener
 *
 * Removes all occurrences of the given listener from <eventListeners>.
 */
mxEventSource.prototype.removeListener = function (funct) {
  if (this.eventListeners != null) {
    var i = 0;

    while (i < this.eventListeners.length) {
      if (this.eventListeners[i + 1] == funct) {
        this.eventListeners.splice(i, 2);
      } else {
        i += 2;
      }
    }
  }
};

/**
 * Function: fireEvent
 *
 * Dispatches the given event to the listeners which are registered for
 * the event. The sender argument is optional. The current execution scope
 * ("this") is used for the listener invocation (see <mxUtils.bind>).
 *
 * Example:
 *
 * (code)
 * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN))
 * (end)
 *
 * Parameters:
 *
 * evt - <mxEventObject> that represents the event.
 * sender - Optional sender to be passed to the listener. Default value is
 * the return value of <getEventSource>.
 */
mxEventSource.prototype.fireEvent = function (evt, sender) {
  if (this.eventListeners != null && this.isEventsEnabled()) {
    if (evt == null) {
      evt = new mxEventObject();
    }

    if (sender == null) {
      sender = this.getEventSource();
    }

    if (sender == null) {
      sender = this;
    }

    var args = [sender, evt];

    for (var i = 0; i < this.eventListeners.length; i += 2) {
      var listen = this.eventListeners[i];

      if (listen == null || listen == evt.getName()) {
        this.eventListeners[i + 1].apply(this, args);
      }
    }
  }
};

/**
 * util/mxEvent.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxEvent = {
  /**
   * Class: mxEvent
   *
   * Cross-browser DOM event support. For internal event handling,
   * <mxEventSource> and the graph event dispatch loop in <mxGraph> are used.
   *
   * Memory Leaks:
   *
   * Use this class for adding and removing listeners to/from DOM nodes. The
   * <removeAllListeners> function is provided to remove all listeners that
   * have been added using <addListener>. The function should be invoked when
   * the last reference is removed in the JavaScript code, typically when the
   * referenced DOM node is removed from the DOM.
   *
   * Function: addListener
   *
   * Binds the function to the specified event on the given element. Use
   * <mxUtils.bind> in order to bind the "this" keyword inside the function
   * to a given execution scope.
   */
  addListener: (function () {
    var updateListenerList = function (element, eventName, funct) {
      if (element.mxListenerList == null) {
        element.mxListenerList = [];
      }

      var entry = { name: eventName, f: funct };
      element.mxListenerList.push(entry);
    };

    if (window.addEventListener) {
      // Checks if passive event listeners are supported
      // see https://github.com/Modernizr/Modernizr/issues/1894
      var supportsPassive = false;

      try {
        document.addEventListener(
          'test',
          function () {},
          Object.defineProperty &&
            Object.defineProperty({}, 'passive', {
              get: function () {
                supportsPassive = true;
              },
            })
        );
      } catch (e) {
        // ignore
      }

      return function (element, eventName, funct) {
        element.addEventListener(eventName, funct, supportsPassive ? { passive: false } : false);
        updateListenerList(element, eventName, funct);
      };
    } else {
      return function (element, eventName, funct) {
        element.attachEvent('on' + eventName, funct);
        updateListenerList(element, eventName, funct);
      };
    }
  })(),

  /**
   * Function: removeListener
   *
   * Removes the specified listener from the given element.
   */
  removeListener: (function () {
    var updateListener = function (element, eventName, funct) {
      if (element.mxListenerList != null) {
        var listenerCount = element.mxListenerList.length;

        for (var i = 0; i < listenerCount; i++) {
          var entry = element.mxListenerList[i];

          if (entry.f == funct) {
            element.mxListenerList.splice(i, 1);
            break;
          }
        }

        if (element.mxListenerList.length == 0) {
          element.mxListenerList = null;
        }
      }
    };

    if (window.removeEventListener) {
      return function (element, eventName, funct) {
        element.removeEventListener(eventName, funct, false);
        updateListener(element, eventName, funct);
      };
    } else {
      return function (element, eventName, funct) {
        element.detachEvent('on' + eventName, funct);
        updateListener(element, eventName, funct);
      };
    }
  })(),

  /**
   * Function: removeAllListeners
   *
   * Removes all listeners from the given element.
   */
  removeAllListeners: function (element) {
    var list = element.mxListenerList;

    if (list != null) {
      while (list.length > 0) {
        var entry = list[0];
        mxEvent.removeListener(element, entry.name, entry.f);
      }
    }
  },

  /**
   * Function: addGestureListeners
   *
   * Adds the given listeners for touch, mouse and/or pointer events. If
   * <mxClient.IS_POINTER> is true then pointer events will be registered,
   * else the respective mouse events will be registered. If <mxClient.IS_POINTER>
   * is false and <mxClient.IS_TOUCH> is true then the respective touch events
   * will be registered as well as the mouse events.
   */
  addGestureListeners: function (node, startListener, moveListener, endListener) {
    if (startListener != null) {
      mxEvent.addListener(node, mxClient.IS_POINTER ? 'pointerdown' : 'mousedown', startListener);
    }

    if (moveListener != null) {
      mxEvent.addListener(node, mxClient.IS_POINTER ? 'pointermove' : 'mousemove', moveListener);
    }

    if (endListener != null) {
      mxEvent.addListener(node, mxClient.IS_POINTER ? 'pointerup' : 'mouseup', endListener);
    }

    if (!mxClient.IS_POINTER && mxClient.IS_TOUCH) {
      if (startListener != null) {
        mxEvent.addListener(node, 'touchstart', startListener);
      }

      if (moveListener != null) {
        mxEvent.addListener(node, 'touchmove', moveListener);
      }

      if (endListener != null) {
        mxEvent.addListener(node, 'touchend', endListener);
      }
    }
  },

  /**
   * Function: removeGestureListeners
   *
   * Removes the given listeners from mousedown, mousemove, mouseup and the
   * respective touch events if <mxClient.IS_TOUCH> is true.
   */
  removeGestureListeners: function (node, startListener, moveListener, endListener) {
    if (startListener != null) {
      mxEvent.removeListener(node, mxClient.IS_POINTER ? 'pointerdown' : 'mousedown', startListener);
    }

    if (moveListener != null) {
      mxEvent.removeListener(node, mxClient.IS_POINTER ? 'pointermove' : 'mousemove', moveListener);
    }

    if (endListener != null) {
      mxEvent.removeListener(node, mxClient.IS_POINTER ? 'pointerup' : 'mouseup', endListener);
    }

    if (!mxClient.IS_POINTER && mxClient.IS_TOUCH) {
      if (startListener != null) {
        mxEvent.removeListener(node, 'touchstart', startListener);
      }

      if (moveListener != null) {
        mxEvent.removeListener(node, 'touchmove', moveListener);
      }

      if (endListener != null) {
        mxEvent.removeListener(node, 'touchend', endListener);
      }
    }
  },

  /**
   * Function: redirectMouseEvents
   *
   * Redirects the mouse events from the given DOM node to the graph dispatch
   * loop using the event and given state as event arguments. State can
   * either be an instance of <mxCellState> or a function that returns an
   * <mxCellState>. The down, move, up and dblClick arguments are optional
   * functions that take the trigger event as arguments and replace the
   * default behaviour.
   */
  redirectMouseEvents: function (node, graph, state, down, move, up, dblClick) {
    var getState = function (evt) {
      return typeof state == 'function' ? state(evt) : state;
    };

    mxEvent.addGestureListeners(
      node,
      function (evt) {
        if (down != null) {
          down(evt);
        } else if (!mxEvent.isConsumed(evt)) {
          graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, getState(evt)));
        }
      },
      function (evt) {
        if (move != null) {
          move(evt);
        } else if (!mxEvent.isConsumed(evt)) {
          graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
        }
      },
      function (evt) {
        if (up != null) {
          up(evt);
        } else if (!mxEvent.isConsumed(evt)) {
          graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
        }
      }
    );

    mxEvent.addListener(node, 'dblclick', function (evt) {
      if (dblClick != null) {
        dblClick(evt);
      } else if (!mxEvent.isConsumed(evt)) {
        var tmp = getState(evt);
        graph.dblClick(evt, tmp != null ? tmp.cell : null);
      }
    });
  },

  /**
   * Function: release
   *
   * Removes the known listeners from the given DOM node and its descendants.
   *
   * Parameters:
   *
   * element - DOM node to remove the listeners from.
   */
  release: function (element) {
    try {
      if (element != null) {
        mxEvent.removeAllListeners(element);

        var children = element.childNodes;

        if (children != null) {
          var childCount = children.length;

          for (var i = 0; i < childCount; i += 1) {
            mxEvent.release(children[i]);
          }
        }
      }
    } catch (e) {
      // ignores errors as this is typically called in cleanup code
    }
  },

  /**
   * Function: addMouseWheelListener
   *
   * Installs the given function as a handler for mouse wheel events. The
   * function has two arguments: the mouse event and a boolean that specifies
   * if the wheel was moved up or down.
   *
   * This has been tested with IE 6 and 7, Firefox (all versions), Opera and
   * Safari. It does currently not work on Safari for Mac.
   *
   * Example:
   *
   * (code)
   * mxEvent.addMouseWheelListener(function (evt, up, pinch)
   * {
   *   mxLog.show();
   *   mxLog.debug('mouseWheel: up='+up);
   * });
   *(end)
   *
   * Parameters:
   *
   * funct - Handler function that takes the event argument, a boolean argument
   * for the mousewheel direction and a boolean to specify if the underlying
   * event was a pinch gesture on a touch device.
   * target - Target for installing the listener in Google Chrome. See
   * https://www.chromestatus.com/features/6662647093133312.
   */
  addMouseWheelListener: function (funct, target) {
    if (funct != null) {
      var wheelHandler = function (evt) {
        // IE does not give an event object but the
        // global event object is the mousewheel event
        // at this point in time.
        if (evt == null) {
          evt = window.event;
        }

        //To prevent window zoom on trackpad pinch
        if (evt.ctrlKey) {
          evt.preventDefault();
        }

        // Handles the event using the given function
        if (Math.abs(evt.deltaX) > 0.5 || Math.abs(evt.deltaY) > 0.5) {
          funct(evt, evt.deltaY == 0 ? -evt.deltaX > 0 : -evt.deltaY > 0);
        }
      };

      target = target != null ? target : window;

      if (mxClient.IS_SF && !mxClient.IS_TOUCH) {
        var scale = 1;

        mxEvent.addListener(target, 'gesturestart', function (evt) {
          mxEvent.consume(evt);
          scale = 1;
        });

        mxEvent.addListener(target, 'gesturechange', function (evt) {
          mxEvent.consume(evt);
          var diff = scale - evt.scale;

          if (Math.abs(diff) > 0.2) {
            funct(evt, diff < 0, true);
            scale = evt.scale;
          }
        });

        mxEvent.addListener(target, 'gestureend', function (evt) {
          mxEvent.consume(evt);
        });
      } else {
        var evtCache = [];
        var dx0 = 0;
        var dy0 = 0;

        // Adds basic listeners for graph event dispatching
        mxEvent.addGestureListeners(
          target,
          mxUtils.bind(this, function (evt) {
            if (!mxEvent.isMouseEvent(evt) && evt.pointerId != null) {
              evtCache.push(evt);
            }
          }),
          mxUtils.bind(this, function (evt) {
            if (!mxEvent.isMouseEvent(evt) && evtCache.length == 2) {
              // Find this event in the cache and update its record with this event
              for (var i = 0; i < evtCache.length; i++) {
                if (evt.pointerId == evtCache[i].pointerId) {
                  evtCache[i] = evt;
                  break;
                }
              }

              // Calculate the distance between the two pointers
              var dx = Math.abs(evtCache[0].clientX - evtCache[1].clientX);
              var dy = Math.abs(evtCache[0].clientY - evtCache[1].clientY);
              var tx = Math.abs(dx - dx0);
              var ty = Math.abs(dy - dy0);

              if (tx > mxEvent.PINCH_THRESHOLD || ty > mxEvent.PINCH_THRESHOLD) {
                var cx = evtCache[0].clientX + (evtCache[1].clientX - evtCache[0].clientX) / 2;
                var cy = evtCache[0].clientY + (evtCache[1].clientY - evtCache[0].clientY) / 2;

                funct(evtCache[0], tx > ty ? dx > dx0 : dy > dy0, true, cx, cy);

                // Cache the distance for the next move event
                dx0 = dx;
                dy0 = dy;
              }
            }
          }),
          mxUtils.bind(this, function (evt) {
            evtCache = [];
            dx0 = 0;
            dy0 = 0;
          })
        );
      }

      mxEvent.addListener(target, 'wheel', wheelHandler);
    }
  },

  /**
   * Function: disableContextMenu
   *
   * Disables the context menu for the given element.
   */
  disableContextMenu: function (element) {
    mxEvent.addListener(element, 'contextmenu', function (evt) {
      if (evt.preventDefault) {
        evt.preventDefault();
      }

      return false;
    });
  },

  /**
   * Function: getSource
   *
   * Returns the event's target or srcElement depending on the browser.
   */
  getSource: function (evt) {
    return evt.srcElement != null ? evt.srcElement : evt.target;
  },

  /**
   * Function: isConsumed
   *
   * Returns true if the event has been consumed using <consume>.
   */
  isConsumed: function (evt) {
    return evt.isConsumed != null && evt.isConsumed;
  },

  /**
   * Function: isTouchEvent
   *
   * Returns true if the event was generated using a touch device (not a pen or mouse).
   */
  isTouchEvent: function (evt) {
    return evt.pointerType != null
      ? evt.pointerType == 'touch' || evt.pointerType === evt.MSPOINTER_TYPE_TOUCH
      : evt.mozInputSource != null
      ? evt.mozInputSource == 5
      : evt.type.indexOf('touch') == 0;
  },

  /**
   * Function: isPenEvent
   *
   * Returns true if the event was generated using a pen (not a touch device or mouse).
   */
  isPenEvent: function (evt) {
    return evt.pointerType != null
      ? evt.pointerType == 'pen' || evt.pointerType === evt.MSPOINTER_TYPE_PEN
      : evt.mozInputSource != null
      ? evt.mozInputSource == 2
      : evt.type.indexOf('pen') == 0;
  },

  /**
   * Function: isMultiTouchEvent
   *
   * Returns true if the event was generated using a touch device (not a pen or mouse).
   */
  isMultiTouchEvent: function (evt) {
    return evt.type != null && evt.type.indexOf('touch') == 0 && evt.touches != null && evt.touches.length > 1;
  },

  /**
   * Function: isMouseEvent
   *
   * Returns true if the event was generated using a mouse (not a pen or touch device).
   */
  isMouseEvent: function (evt) {
    return evt.pointerType != null
      ? evt.pointerType == 'mouse' || evt.pointerType === evt.MSPOINTER_TYPE_MOUSE
      : evt.mozInputSource != null
      ? evt.mozInputSource == 1
      : evt.type.indexOf('mouse') == 0;
  },

  /**
   * Function: isLeftMouseButton
   *
   * Returns true if the left mouse button is pressed for the given event.
   * To check if a button is pressed during a mouseMove you should use the
   * <mxGraph.isMouseDown> property. Note that this returns true in Firefox
   * for control+left-click on the Mac.
   */
  isLeftMouseButton: function (evt) {
    // Special case for mousemove and mousedown we check the buttons
    // if it exists because which is 0 even if no button is pressed
    if ('buttons' in evt && (evt.type == 'mousedown' || evt.type == 'mousemove')) {
      return evt.buttons == 1;
    } else if ('which' in evt) {
      return evt.which === 1;
    } else {
      return evt.button === 1;
    }
  },

  /**
   * Function: isMiddleMouseButton
   *
   * Returns true if the middle mouse button is pressed for the given event.
   * To check if a button is pressed during a mouseMove you should use the
   * <mxGraph.isMouseDown> property.
   */
  isMiddleMouseButton: function (evt) {
    if ('which' in evt) {
      return evt.which === 2;
    } else {
      return evt.button === 4;
    }
  },

  /**
   * Function: isRightMouseButton
   *
   * Returns true if the right mouse button was pressed. Note that this
   * button might not be available on some systems. For handling a popup
   * trigger <isPopupTrigger> should be used.
   */
  isRightMouseButton: function (evt) {
    if ('which' in evt) {
      return evt.which === 3;
    } else {
      return evt.button === 2;
    }
  },

  /**
   * Function: isPopupTrigger
   *
   * Returns true if the event is a popup trigger. This implementation
   * returns true if the right button or the left button and control was
   * pressed on a Mac.
   */
  isPopupTrigger: function (evt) {
    return (
      mxEvent.isRightMouseButton(evt) ||
      (mxClient.IS_MAC &&
        mxEvent.isControlDown(evt) &&
        !mxEvent.isShiftDown(evt) &&
        !mxEvent.isMetaDown(evt) &&
        !mxEvent.isAltDown(evt))
    );
  },

  /**
   * Function: isShiftDown
   *
   * Returns true if the shift key is pressed for the given event.
   */
  isShiftDown: function (evt) {
    return evt != null ? evt.shiftKey : false;
  },

  /**
   * Function: isAltDown
   *
   * Returns true if the alt key is pressed for the given event.
   */
  isAltDown: function (evt) {
    return evt != null ? evt.altKey : false;
  },

  /**
   * Function: isControlDown
   *
   * Returns true if the control key is pressed for the given event.
   */
  isControlDown: function (evt) {
    return evt != null ? evt.ctrlKey : false;
  },

  /**
   * Function: isMetaDown
   *
   * Returns true if the meta key is pressed for the given event.
   */
  isMetaDown: function (evt) {
    return evt != null ? evt.metaKey : false;
  },

  /**
   * Function: getMainEvent
   *
   * Returns the touch or mouse event that contains the mouse coordinates.
   */
  getMainEvent: function (e) {
    if ((e.type == 'touchstart' || e.type == 'touchmove') && e.touches != null && e.touches[0] != null) {
      e = e.touches[0];
    } else if (e.type == 'touchend' && e.changedTouches != null && e.changedTouches[0] != null) {
      e = e.changedTouches[0];
    }

    return e;
  },

  /**
   * Function: getClientX
   *
   * Returns true if the meta key is pressed for the given event.
   */
  getClientX: function (e) {
    return mxEvent.getMainEvent(e).clientX;
  },

  /**
   * Function: getClientY
   *
   * Returns true if the meta key is pressed for the given event.
   */
  getClientY: function (e) {
    return mxEvent.getMainEvent(e).clientY;
  },

  /**
   * Function: consume
   *
   * Consumes the given event.
   *
   * Parameters:
   *
   * evt - Native event to be consumed.
   * preventDefault - Optional boolean to prevent the default for the event.
   * Default is true.
   * stopPropagation - Option boolean to stop event propagation. Default is
   * true.
   */
  consume: function (evt, preventDefault, stopPropagation) {
    preventDefault = preventDefault != null ? preventDefault : true;
    stopPropagation = stopPropagation != null ? stopPropagation : true;

    if (preventDefault) {
      if (evt.preventDefault) {
        if (stopPropagation) {
          evt.stopPropagation();
        }

        evt.preventDefault();
      } else if (stopPropagation) {
        evt.cancelBubble = true;
      }
    }

    // Opera
    evt.isConsumed = true;

    // Other browsers
    if (!evt.preventDefault) {
      evt.returnValue = false;
    }
  },

  //
  // Special handles in mouse events
  //

  /**
   * Variable: LABEL_HANDLE
   *
   * Index for the label handle in an mxMouseEvent. This should be a negative
   * value that does not interfere with any possible handle indices. Default
   * is -1.
   */
  LABEL_HANDLE: -1,

  /**
   * Variable: ROTATION_HANDLE
   *
   * Index for the rotation handle in an mxMouseEvent. This should be a
   * negative value that does not interfere with any possible handle indices.
   * Default is -2.
   */
  ROTATION_HANDLE: -2,

  /**
   * Variable: CUSTOM_HANDLE
   *
   * Start index for the custom handles in an mxMouseEvent. This should be a
   * negative value and is the start index which is decremented for each
   * custom handle. Default is -100.
   */
  CUSTOM_HANDLE: -100,

  /**
   * Variable: VIRTUAL_HANDLE
   *
   * Start index for the virtual handles in an mxMouseEvent. This should be a
   * negative value and is the start index which is decremented for each
   * virtual handle. Default is -100000. This assumes that there are no more
   * than VIRTUAL_HANDLE - CUSTOM_HANDLE custom handles.
   *
   */
  VIRTUAL_HANDLE: -100000,

  //
  // Event names
  //

  /**
   * Variable: MOUSE_DOWN
   *
   * Specifies the event name for mouseDown.
   */
  MOUSE_DOWN: 'mouseDown',

  /**
   * Variable: MOUSE_MOVE
   *
   * Specifies the event name for mouseMove.
   */
  MOUSE_MOVE: 'mouseMove',

  /**
   * Variable: MOUSE_UP
   *
   * Specifies the event name for mouseUp.
   */
  MOUSE_UP: 'mouseUp',

  /**
   * Variable: ACTIVATE
   *
   * Specifies the event name for activate.
   */
  ACTIVATE: 'activate',

  /**
   * Variable: RESIZE_START
   *
   * Specifies the event name for resizeStart.
   */
  RESIZE_START: 'resizeStart',

  /**
   * Variable: RESIZE
   *
   * Specifies the event name for resize.
   */
  RESIZE: 'resize',

  /**
   * Variable: RESIZE_END
   *
   * Specifies the event name for resizeEnd.
   */
  RESIZE_END: 'resizeEnd',

  /**
   * Variable: MOVE_START
   *
   * Specifies the event name for moveStart.
   */
  MOVE_START: 'moveStart',

  /**
   * Variable: MOVE
   *
   * Specifies the event name for move.
   */
  MOVE: 'move',

  /**
   * Variable: MOVE_END
   *
   * Specifies the event name for moveEnd.
   */
  MOVE_END: 'moveEnd',

  /**
   * Variable: PAN_START
   *
   * Specifies the event name for panStart.
   */
  PAN_START: 'panStart',

  /**
   * Variable: PAN
   *
   * Specifies the event name for pan.
   */
  PAN: 'pan',

  /**
   * Variable: PAN_END
   *
   * Specifies the event name for panEnd.
   */
  PAN_END: 'panEnd',

  /**
   * Variable: MINIMIZE
   *
   * Specifies the event name for minimize.
   */
  MINIMIZE: 'minimize',

  /**
   * Variable: NORMALIZE
   *
   * Specifies the event name for normalize.
   */
  NORMALIZE: 'normalize',

  /**
   * Variable: MAXIMIZE
   *
   * Specifies the event name for maximize.
   */
  MAXIMIZE: 'maximize',

  /**
   * Variable: HIDE
   *
   * Specifies the event name for hide.
   */
  HIDE: 'hide',

  /**
   * Variable: SHOW
   *
   * Specifies the event name for show.
   */
  SHOW: 'show',

  /**
   * Variable: CLOSE
   *
   * Specifies the event name for close.
   */
  CLOSE: 'close',

  /**
   * Variable: DESTROY
   *
   * Specifies the event name for destroy.
   */
  DESTROY: 'destroy',

  /**
   * Variable: REFRESH
   *
   * Specifies the event name for refresh.
   */
  REFRESH: 'refresh',

  /**
   * Variable: SIZE
   *
   * Specifies the event name for size.
   */
  SIZE: 'size',

  /**
   * Variable: SELECT
   *
   * Specifies the event name for select.
   */
  SELECT: 'select',

  /**
   * Variable: FIRED
   *
   * Specifies the event name for fired.
   */
  FIRED: 'fired',

  /**
   * Variable: FIRE_MOUSE_EVENT
   *
   * Specifies the event name for fireMouseEvent.
   */
  FIRE_MOUSE_EVENT: 'fireMouseEvent',

  /**
   * Variable: GESTURE
   *
   * Specifies the event name for gesture.
   */
  GESTURE: 'gesture',

  /**
   * Variable: TAP_AND_HOLD
   *
   * Specifies the event name for tapAndHold.
   */
  TAP_AND_HOLD: 'tapAndHold',

  /**
   * Variable: GET
   *
   * Specifies the event name for get.
   */
  GET: 'get',

  /**
   * Variable: RECEIVE
   *
   * Specifies the event name for receive.
   */
  RECEIVE: 'receive',

  /**
   * Variable: CONNECT
   *
   * Specifies the event name for connect.
   */
  CONNECT: 'connect',

  /**
   * Variable: DISCONNECT
   *
   * Specifies the event name for disconnect.
   */
  DISCONNECT: 'disconnect',

  /**
   * Variable: SUSPEND
   *
   * Specifies the event name for suspend.
   */
  SUSPEND: 'suspend',

  /**
   * Variable: RESUME
   *
   * Specifies the event name for suspend.
   */
  RESUME: 'resume',

  /**
   * Variable: MARK
   *
   * Specifies the event name for mark.
   */
  MARK: 'mark',

  /**
   * Variable: ROOT
   *
   * Specifies the event name for root.
   */
  ROOT: 'root',

  /**
   * Variable: POST
   *
   * Specifies the event name for post.
   */
  POST: 'post',

  /**
   * Variable: OPEN
   *
   * Specifies the event name for open.
   */
  OPEN: 'open',

  /**
   * Variable: SAVE
   *
   * Specifies the event name for open.
   */
  SAVE: 'save',

  /**
   * Variable: BEFORE_ADD_VERTEX
   *
   * Specifies the event name for beforeAddVertex.
   */
  BEFORE_ADD_VERTEX: 'beforeAddVertex',

  /**
   * Variable: ADD_VERTEX
   *
   * Specifies the event name for addVertex.
   */
  ADD_VERTEX: 'addVertex',

  /**
   * Variable: AFTER_ADD_VERTEX
   *
   * Specifies the event name for afterAddVertex.
   */
  AFTER_ADD_VERTEX: 'afterAddVertex',

  /**
   * Variable: DONE
   *
   * Specifies the event name for done.
   */
  DONE: 'done',

  /**
   * Variable: EXECUTE
   *
   * Specifies the event name for execute.
   */
  EXECUTE: 'execute',

  /**
   * Variable: EXECUTED
   *
   * Specifies the event name for executed.
   */
  EXECUTED: 'executed',

  /**
   * Variable: BEGIN_UPDATE
   *
   * Specifies the event name for beginUpdate.
   */
  BEGIN_UPDATE: 'beginUpdate',

  /**
   * Variable: START_EDIT
   *
   * Specifies the event name for startEdit.
   */
  START_EDIT: 'startEdit',

  /**
   * Variable: END_UPDATE
   *
   * Specifies the event name for endUpdate.
   */
  END_UPDATE: 'endUpdate',

  /**
   * Variable: END_EDIT
   *
   * Specifies the event name for endEdit.
   */
  END_EDIT: 'endEdit',

  /**
   * Variable: BEFORE_UNDO
   *
   * Specifies the event name for beforeUndo.
   */
  BEFORE_UNDO: 'beforeUndo',

  /**
   * Variable: UNDO
   *
   * Specifies the event name for undo.
   */
  UNDO: 'undo',

  /**
   * Variable: REDO
   *
   * Specifies the event name for redo.
   */
  REDO: 'redo',

  /**
   * Variable: CHANGE
   *
   * Specifies the event name for change.
   */
  CHANGE: 'change',

  /**
   * Variable: NOTIFY
   *
   * Specifies the event name for notify.
   */
  NOTIFY: 'notify',

  /**
   * Variable: LAYOUT_CELLS
   *
   * Specifies the event name for layoutCells.
   */
  LAYOUT_CELLS: 'layoutCells',

  /**
   * Variable: CLICK
   *
   * Specifies the event name for click.
   */
  CLICK: 'click',

  /**
   * Variable: SCALE
   *
   * Specifies the event name for scale.
   */
  SCALE: 'scale',

  /**
   * Variable: TRANSLATE
   *
   * Specifies the event name for translate.
   */
  TRANSLATE: 'translate',

  /**
   * Variable: SCALE_AND_TRANSLATE
   *
   * Specifies the event name for scaleAndTranslate.
   */
  SCALE_AND_TRANSLATE: 'scaleAndTranslate',

  /**
   * Variable: UP
   *
   * Specifies the event name for up.
   */
  UP: 'up',

  /**
   * Variable: DOWN
   *
   * Specifies the event name for down.
   */
  DOWN: 'down',

  /**
   * Variable: ADD
   *
   * Specifies the event name for add.
   */
  ADD: 'add',

  /**
   * Variable: REMOVE
   *
   * Specifies the event name for remove.
   */
  REMOVE: 'remove',

  /**
   * Variable: CLEAR
   *
   * Specifies the event name for clear.
   */
  CLEAR: 'clear',

  /**
   * Variable: ADD_CELLS
   *
   * Specifies the event name for addCells.
   */
  ADD_CELLS: 'addCells',

  /**
   * Variable: CELLS_ADDED
   *
   * Specifies the event name for cellsAdded.
   */
  CELLS_ADDED: 'cellsAdded',

  /**
   * Variable: MOVE_CELLS
   *
   * Specifies the event name for moveCells.
   */
  MOVE_CELLS: 'moveCells',

  /**
   * Variable: CELLS_MOVED
   *
   * Specifies the event name for cellsMoved.
   */
  CELLS_MOVED: 'cellsMoved',

  /**
   * Variable: RESIZE_CELLS
   *
   * Specifies the event name for resizeCells.
   */
  RESIZE_CELLS: 'resizeCells',

  /**
   * Variable: CELLS_RESIZED
   *
   * Specifies the event name for cellsResized.
   */
  CELLS_RESIZED: 'cellsResized',

  /**
   * Variable: TOGGLE_CELLS
   *
   * Specifies the event name for toggleCells.
   */
  TOGGLE_CELLS: 'toggleCells',

  /**
   * Variable: CELLS_TOGGLED
   *
   * Specifies the event name for cellsToggled.
   */
  CELLS_TOGGLED: 'cellsToggled',

  /**
   * Variable: ORDER_CELLS
   *
   * Specifies the event name for orderCells.
   */
  ORDER_CELLS: 'orderCells',

  /**
   * Variable: CELLS_ORDERED
   *
   * Specifies the event name for cellsOrdered.
   */
  CELLS_ORDERED: 'cellsOrdered',

  /**
   * Variable: REMOVE_CELLS
   *
   * Specifies the event name for removeCells.
   */
  REMOVE_CELLS: 'removeCells',

  /**
   * Variable: CELLS_REMOVED
   *
   * Specifies the event name for cellsRemoved.
   */
  CELLS_REMOVED: 'cellsRemoved',

  /**
   * Variable: GROUP_CELLS
   *
   * Specifies the event name for groupCells.
   */
  GROUP_CELLS: 'groupCells',

  /**
   * Variable: UNGROUP_CELLS
   *
   * Specifies the event name for ungroupCells.
   */
  UNGROUP_CELLS: 'ungroupCells',

  /**
   * Variable: REMOVE_CELLS_FROM_PARENT
   *
   * Specifies the event name for removeCellsFromParent.
   */
  REMOVE_CELLS_FROM_PARENT: 'removeCellsFromParent',

  /**
   * Variable: FOLD_CELLS
   *
   * Specifies the event name for foldCells.
   */
  FOLD_CELLS: 'foldCells',

  /**
   * Variable: CELLS_FOLDED
   *
   * Specifies the event name for cellsFolded.
   */
  CELLS_FOLDED: 'cellsFolded',

  /**
   * Variable: ALIGN_CELLS
   *
   * Specifies the event name for alignCells.
   */
  ALIGN_CELLS: 'alignCells',

  /**
   * Variable: LABEL_CHANGED
   *
   * Specifies the event name for labelChanged.
   */
  LABEL_CHANGED: 'labelChanged',

  /**
   * Variable: CONNECT_CELL
   *
   * Specifies the event name for connectCell.
   */
  CONNECT_CELL: 'connectCell',

  /**
   * Variable: CELL_CONNECTED
   *
   * Specifies the event name for cellConnected.
   */
  CELL_CONNECTED: 'cellConnected',

  /**
   * Variable: SPLIT_EDGE
   *
   * Specifies the event name for splitEdge.
   */
  SPLIT_EDGE: 'splitEdge',

  /**
   * Variable: FLIP_EDGE
   *
   * Specifies the event name for flipEdge.
   */
  FLIP_EDGE: 'flipEdge',

  /**
   * Variable: START_EDITING
   *
   * Specifies the event name for startEditing.
   */
  START_EDITING: 'startEditing',

  /**
   * Variable: EDITING_STARTED
   *
   * Specifies the event name for editingStarted.
   */
  EDITING_STARTED: 'editingStarted',

  /**
   * Variable: EDITING_STOPPED
   *
   * Specifies the event name for editingStopped.
   */
  EDITING_STOPPED: 'editingStopped',

  /**
   * Variable: ADD_OVERLAY
   *
   * Specifies the event name for addOverlay.
   */
  ADD_OVERLAY: 'addOverlay',

  /**
   * Variable: REMOVE_OVERLAY
   *
   * Specifies the event name for removeOverlay.
   */
  REMOVE_OVERLAY: 'removeOverlay',

  /**
   * Variable: UPDATE_CELL_SIZE
   *
   * Specifies the event name for updateCellSize.
   */
  UPDATE_CELL_SIZE: 'updateCellSize',

  /**
   * Variable: ESCAPE
   *
   * Specifies the event name for escape.
   */
  ESCAPE: 'escape',

  /**
   * Variable: DOUBLE_CLICK
   *
   * Specifies the event name for doubleClick.
   */
  DOUBLE_CLICK: 'doubleClick',

  /**
   * Variable: START
   *
   * Specifies the event name for start.
   */
  START: 'start',

  /**
   * Variable: RESET
   *
   * Specifies the event name for reset.
   */
  RESET: 'reset',

  /**
   * Variable: PINCH_THRESHOLD
   *
   * Threshold for pinch gestures to fire a mouse wheel event.
   * Default value is 10.
   */
  PINCH_THRESHOLD: 10,
};

/**
 * util/mxXmlRequest.js
 */
/**
 * Copyright (c) 2006-2020, JGraph Ltd
 * Copyright (c) 2006-2020, draw.io AG
 */
/**
 * Class: mxXmlRequest
 *
 * XML HTTP request wrapper. See also: <mxUtils.get>, <mxUtils.post> and
 * <mxUtils.load>. This class provides a cross-browser abstraction for Ajax
 * requests.
 *
 * Encoding:
 *
 * For encoding parameter values, the built-in encodeURIComponent JavaScript
 * method must be used. For automatic encoding of post data in <mxEditor> the
 * <mxEditor.escapePostData> switch can be set to true (default). The encoding
 * will be carried out using the conte type of the page. That is, the page
 * containting the editor should contain a meta tag in the header, eg.
 * <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 *
 * Example:
 *
 * (code)
 * var onload = function(req)
 * {
 *   mxUtils.alert(req.getDocumentElement());
 * }
 *
 * var onerror = function(req)
 * {
 *   mxUtils.alert('Error');
 * }
 * new mxXmlRequest(url, 'key=value').send(onload, onerror);
 * (end)
 *
 * Sends an asynchronous POST request to the specified URL.
 *
 * Example:
 *
 * (code)
 * var req = new mxXmlRequest(url, 'key=value', 'POST', false);
 * req.send();
 * mxUtils.alert(req.getDocumentElement());
 * (end)
 *
 * Sends a synchronous POST request to the specified URL.
 *
 * Example:
 *
 * (code)
 * var encoder = new mxCodec();
 * var result = encoder.encode(graph.getModel());
 * var xml = encodeURIComponent(mxUtils.getXml(result));
 * new mxXmlRequest(url, 'xml='+xml).send();
 * (end)
 *
 * Sends an encoded graph model to the specified URL using xml as the
 * parameter name. The parameter can then be retrieved in C# as follows:
 *
 * (code)
 * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]);
 * (end)
 *
 * Or in Java as follows:
 *
 * (code)
 * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;");
 * (end)
 *
 * Note that the linefeeds should only be replaced if the XML is
 * processed in Java, for example when creating an image.
 *
 * Constructor: mxXmlRequest
 *
 * Constructs an XML HTTP request.
 *
 * Parameters:
 *
 * url - Target URL of the request.
 * params - Form encoded parameters to send with a POST request.
 * method - String that specifies the request method. Possible values are
 * POST and GET. Default is POST.
 * async - Boolean specifying if an asynchronous request should be used.
 * Default is true.
 * username - String specifying the username to be used for the request.
 * password - String specifying the password to be used for the request.
 */
function mxXmlRequest(url, params, method, async, username, password) {
  this.url = url;
  this.params = params;
  this.method = method || 'POST';
  this.async = async != null ? async : true;
  this.username = username;
  this.password = password;
}

/**
 * Variable: url
 *
 * Holds the target URL of the request.
 */
mxXmlRequest.prototype.url = null;

/**
 * Variable: params
 *
 * Holds the form encoded data for the POST request.
 */
mxXmlRequest.prototype.params = null;

/**
 * Variable: method
 *
 * Specifies the request method. Possible values are POST and GET. Default
 * is POST.
 */
mxXmlRequest.prototype.method = null;

/**
 * Variable: async
 *
 * Boolean indicating if the request is asynchronous.
 */
mxXmlRequest.prototype.async = null;

/**
 * Variable: binary
 *
 * Boolean indicating if the request is binary. This option is ignored in IE.
 * In all other browsers the requested mime type is set to
 * text/plain; charset=x-user-defined. Default is false.
 */
mxXmlRequest.prototype.binary = false;

/**
 * Variable: withCredentials
 *
 * Specifies if withCredentials should be used in HTML5-compliant browsers. Default is
 * false.
 */
mxXmlRequest.prototype.withCredentials = false;

/**
 * Variable: username
 *
 * Specifies the username to be used for authentication.
 */
mxXmlRequest.prototype.username = null;

/**
 * Variable: password
 *
 * Specifies the password to be used for authentication.
 */
mxXmlRequest.prototype.password = null;

/**
 * Variable: request
 *
 * Holds the inner, browser-specific request object.
 */
mxXmlRequest.prototype.request = null;

/**
 * Variable: decodeSimulateValues
 *
 * Specifies if request values should be decoded as URIs before setting the
 * textarea value in <simulate>. Defaults to false for backwards compatibility,
 * to avoid another decode on the server this should be set to true.
 */
mxXmlRequest.prototype.decodeSimulateValues = false;

/**
 * Function: isBinary
 *
 * Returns <binary>.
 */
mxXmlRequest.prototype.isBinary = function () {
  return this.binary;
};

/**
 * Function: setBinary
 *
 * Sets <binary>.
 */
mxXmlRequest.prototype.setBinary = function (value) {
  this.binary = value;
};

/**
 * Function: getText
 *
 * Returns the response as a string.
 */
mxXmlRequest.prototype.getText = function () {
  return this.request.responseText;
};

/**
 * Function: isReady
 *
 * Returns true if the response is ready.
 */
mxXmlRequest.prototype.isReady = function () {
  return this.request.readyState == 4;
};

/**
 * Function: getDocumentElement
 *
 * Returns the document element of the response XML document.
 */
mxXmlRequest.prototype.getDocumentElement = function () {
  var doc = this.getXml();

  if (doc != null) {
    return doc.documentElement;
  }

  return null;
};

/**
 * Function: getXml
 *
 * Returns the response as an XML document. Use <getDocumentElement> to get
 * the document element of the XML document.
 */
mxXmlRequest.prototype.getXml = function () {
  var xml = this.request.responseXML;

  // Handles missing response headers in IE, the first condition handles
  // the case where responseXML is there, but using its nodes leads to
  // type errors in the mxCellCodec when putting the nodes into a new
  // document. This happens in IE9 standards mode and with XML user
  // objects only, as they are used directly as values in cells.
  if (document.documentMode >= 9 || xml == null || xml.documentElement == null) {
    xml = mxUtils.parseXml(this.request.responseText);
  }

  return xml;
};

/**
 * Function: getStatus
 *
 * Returns the status as a number, eg. 404 for "Not found" or 200 for "OK".
 * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought.
 */
mxXmlRequest.prototype.getStatus = function () {
  return this.request != null ? this.request.status : null;
};

/**
 * Function: create
 *
 * Creates and returns the inner <request> object.
 */
mxXmlRequest.prototype.create = (function () {
  if (window.XMLHttpRequest) {
    return function () {
      var req = new XMLHttpRequest();

      // TODO: Check for overrideMimeType required here?
      if (this.isBinary() && req.overrideMimeType) {
        req.overrideMimeType('text/plain; charset=x-user-defined');
      }

      return req;
    };
  } else if (typeof ActiveXObject != 'undefined') {
    return function () {
      // TODO: Implement binary option
      return new ActiveXObject('Microsoft.XMLHTTP');
    };
  }
})();

/**
 * Function: send
 *
 * Send the <request> to the target URL using the specified functions to
 * process the response asychronously.
 *
 * Note: Due to technical limitations, onerror is currently ignored.
 *
 * Parameters:
 *
 * onload - Function to be invoked if a successful response was received.
 * onerror - Function to be called on any error. Unused in this implementation, intended for overriden function.
 * timeout - Optional timeout in ms before calling ontimeout.
 * ontimeout - Optional function to execute on timeout.
 */
mxXmlRequest.prototype.send = function (onload, onerror, timeout, ontimeout) {
  this.request = this.create();

  if (this.request != null) {
    if (onload != null) {
      this.request.onreadystatechange = mxUtils.bind(this, function () {
        if (this.isReady()) {
          onload(this);
          this.request.onreadystatechange = null;
        }
      });
    }

    this.request.open(this.method, this.url, this.async, this.username, this.password);
    this.setRequestHeaders(this.request, this.params);

    if (window.XMLHttpRequest && this.withCredentials) {
      this.request.withCredentials = 'true';
    }

    if (
      !mxClient.IS_QUIRKS &&
      (document.documentMode == null || document.documentMode > 9) &&
      window.XMLHttpRequest &&
      timeout != null &&
      ontimeout != null
    ) {
      this.request.timeout = timeout;
      this.request.ontimeout = ontimeout;
    }

    this.request.send(this.params);
  }
};

/**
 * Function: setRequestHeaders
 *
 * Sets the headers for the given request and parameters. This sets the
 * content-type to application/x-www-form-urlencoded if any params exist.
 *
 * Example:
 *
 * (code)
 * request.setRequestHeaders = function(request, params)
 * {
 *   if (params != null)
 *   {
 *     request.setRequestHeader('Content-Type',
 *             'multipart/form-data');
 *     request.setRequestHeader('Content-Length',
 *             params.length);
 *   }
 * };
 * (end)
 *
 * Use the code above before calling <send> if you require a
 * multipart/form-data request.
 */
mxXmlRequest.prototype.setRequestHeaders = function (request, params) {
  if (params != null) {
    request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  }
};

/**
 * Function: simulate
 *
 * Creates and posts a request to the given target URL using a dynamically
 * created form inside the given document.
 *
 * Parameters:
 *
 * docs - Document that contains the form element.
 * target - Target to send the form result to.
 */
mxXmlRequest.prototype.simulate = function (doc, target) {
  doc = doc || document;
  var old = null;

  if (doc == document) {
    old = window.onbeforeunload;
    window.onbeforeunload = null;
  }

  var form = doc.createElement('form');
  form.setAttribute('method', this.method);
  form.setAttribute('action', this.url);

  if (target != null) {
    form.setAttribute('target', target);
  }

  form.style.display = 'none';
  form.style.visibility = 'hidden';

  var pars = this.params.indexOf('&') > 0 ? this.params.split('&') : this.params.split();

  // Adds the parameters as textareas to the form
  for (var i = 0; i < pars.length; i++) {
    var pos = pars[i].indexOf('=');

    if (pos > 0) {
      var name = pars[i].substring(0, pos);
      var value = pars[i].substring(pos + 1);

      if (this.decodeSimulateValues) {
        value = decodeURIComponent(value);
      }

      var textarea = doc.createElement('textarea');
      textarea.setAttribute('wrap', 'off');
      textarea.setAttribute('name', name);
      mxUtils.write(textarea, value);
      form.appendChild(textarea);
    }
  }

  doc.body.appendChild(form);
  form.submit();

  if (form.parentNode != null) {
    form.parentNode.removeChild(form);
  }

  if (old != null) {
    window.onbeforeunload = old;
  }
};

/**
 * util/mxClipboard.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxClipboard = {
  /**
   * Class: mxClipboard
   *
   * Singleton that implements a clipboard for graph cells.
   *
   * Example:
   *
   * (code)
   * mxClipboard.copy(graph);
   * mxClipboard.paste(graph2);
   * (end)
   *
   * This copies the selection cells from the graph to the clipboard and
   * pastes them into graph2.
   *
   * For fine-grained control of the clipboard data the <mxGraph.canExportCell>
   * and <mxGraph.canImportCell> functions can be overridden.
   *
   * To restore previous parents for pasted cells, the implementation for
   * <copy> and <paste> can be changed as follows.
   *
   * (code)
   * mxClipboard.copy = function(graph, cells)
   * {
   *   cells = cells || graph.getSelectionCells();
   *   var result = graph.getExportableCells(cells);
   *
   *   mxClipboard.parents = new Object();
   *
   *   for (var i = 0; i < result.length; i++)
   *   {
   *     mxClipboard.parents[i] = graph.model.getParent(cells[i]);
   *   }
   *
   *   mxClipboard.insertCount = 1;
   *   mxClipboard.setCells(graph.cloneCells(result));
   *
   *   return result;
   * };
   *
   * mxClipboard.paste = function(graph)
   * {
   *   if (!mxClipboard.isEmpty())
   *   {
   *     var cells = graph.getImportableCells(mxClipboard.getCells());
   *     var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
   *     var parent = graph.getDefaultParent();
   *
   *     graph.model.beginUpdate();
   *     try
   *     {
   *       for (var i = 0; i < cells.length; i++)
   *       {
   *         var tmp = (mxClipboard.parents != null && graph.model.contains(mxClipboard.parents[i])) ?
   *              mxClipboard.parents[i] : parent;
   *         cells[i] = graph.importCells([cells[i]], delta, delta, tmp)[0];
   *       }
   *     }
   *     finally
   *     {
   *       graph.model.endUpdate();
   *     }
   *
   *     // Increments the counter and selects the inserted cells
   *     mxClipboard.insertCount++;
   *     graph.setSelectionCells(cells);
   *   }
   * };
   * (end)
   *
   * Variable: STEPSIZE
   *
   * Defines the step size to offset the cells after each paste operation.
   * Default is 10.
   */
  STEPSIZE: 10,

  /**
   * Variable: insertCount
   *
   * Counts the number of times the clipboard data has been inserted.
   */
  insertCount: 1,

  /**
   * Variable: cells
   *
   * Holds the array of <mxCells> currently in the clipboard.
   */
  cells: null,

  /**
   * Function: setCells
   *
   * Sets the cells in the clipboard. Fires a <mxEvent.CHANGE> event.
   */
  setCells: function (cells) {
    mxClipboard.cells = cells;
  },

  /**
   * Function: getCells
   *
   * Returns  the cells in the clipboard.
   */
  getCells: function () {
    return mxClipboard.cells;
  },

  /**
   * Function: isEmpty
   *
   * Returns true if the clipboard currently has not data stored.
   */
  isEmpty: function () {
    return mxClipboard.getCells() == null;
  },

  /**
   * Function: cut
   *
   * Cuts the given array of <mxCells> from the specified graph.
   * If cells is null then the selection cells of the graph will
   * be used. Returns the cells that have been cut from the graph.
   *
   * Parameters:
   *
   * graph - <mxGraph> that contains the cells to be cut.
   * cells - Optional array of <mxCells> to be cut.
   */
  cut: function (graph, cells) {
    cells = mxClipboard.copy(graph, cells);
    mxClipboard.insertCount = 0;
    mxClipboard.removeCells(graph, cells);

    return cells;
  },

  /**
   * Function: removeCells
   *
   * Hook to remove the given cells from the given graph after
   * a cut operation.
   *
   * Parameters:
   *
   * graph - <mxGraph> that contains the cells to be cut.
   * cells - Array of <mxCells> to be cut.
   */
  removeCells: function (graph, cells) {
    graph.removeCells(cells);
  },

  /**
   * Function: copy
   *
   * Copies the given array of <mxCells> from the specified
   * graph to <cells>. Returns the original array of cells that has
   * been cloned. Descendants of cells in the array are ignored.
   *
   * Parameters:
   *
   * graph - <mxGraph> that contains the cells to be copied.
   * cells - Optional array of <mxCells> to be copied.
   */
  copy: function (graph, cells) {
    cells = cells || graph.getSelectionCells();
    var result = graph.getExportableCells(graph.model.getTopmostCells(cells));
    mxClipboard.insertCount = 1;
    mxClipboard.setCells(graph.cloneCells(result));

    return result;
  },

  /**
   * Function: paste
   *
   * Pastes the <cells> into the specified graph restoring
   * the relation to <parents>, if possible. If the parents
   * are no longer in the graph or invisible then the
   * cells are added to the graph's default or into the
   * swimlane under the cell's new location if one exists.
   * The cells are added to the graph using <mxGraph.importCells>
   * and returned.
   *
   * Parameters:
   *
   * graph - <mxGraph> to paste the <cells> into.
   */
  paste: function (graph) {
    var cells = null;

    if (!mxClipboard.isEmpty()) {
      cells = graph.getImportableCells(mxClipboard.getCells());
      var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
      var parent = graph.getDefaultParent();
      cells = graph.importCells(cells, delta, delta, parent);

      // Increments the counter and selects the inserted cells
      mxClipboard.insertCount++;
      graph.setSelectionCells(cells);
    }

    return cells;
  },
};

/**
 * util/mxWindow.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxWindow
 *
 * Basic window inside a document.
 *
 * Examples:
 *
 * Creating a simple window.
 *
 * (code)
 * var tb = document.createElement('div');
 * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true);
 * wnd.setVisible(true);
 * (end)
 *
 * Creating a window that contains an iframe.
 *
 * (code)
 * var frame = document.createElement('iframe');
 * frame.setAttribute('width', '192px');
 * frame.setAttribute('height', '172px');
 * frame.setAttribute('src', 'http://www.example.com/');
 * frame.style.backgroundColor = 'white';
 *
 * var w = document.body.clientWidth;
 * var h = (document.body.clientHeight || document.documentElement.clientHeight);
 * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200);
 * wnd.setVisible(true);
 * (end)
 *
 * To limit the movement of a window, eg. to keep it from being moved beyond
 * the top, left corner the following method can be overridden (recommended):
 *
 * (code)
 * wnd.setLocation = function(x, y)
 * {
 *   x = Math.max(0, x);
 *   y = Math.max(0, y);
 *   mxWindow.prototype.setLocation.apply(this, arguments);
 * };
 * (end)
 *
 * Or the following event handler can be used:
 *
 * (code)
 * wnd.addListener(mxEvent.MOVE, function(e)
 * {
 *   wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY()));
 * });
 * (end)
 *
 * To keep a window inside the current window:
 *
 * (code)
 * mxEvent.addListener(window, 'resize', mxUtils.bind(this, function()
 * {
 *   var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
 *   var ih = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
 *
 *   var x = this.window.getX();
 *   var y = this.window.getY();
 *
 *   if (x + this.window.table.clientWidth > iw)
 *   {
 *     x = Math.max(0, iw - this.window.table.clientWidth);
 *   }
 *
 *   if (y + this.window.table.clientHeight > ih)
 *   {
 *     y = Math.max(0, ih - this.window.table.clientHeight);
 *   }
 *
 *   if (this.window.getX() != x || this.window.getY() != y)
 *   {
 *     this.window.setLocation(x, y);
 *   }
 * }));
 * (end)
 *
 * Event: mxEvent.MOVE_START
 *
 * Fires before the window is moved. The <code>event</code> property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.MOVE
 *
 * Fires while the window is being moved. The <code>event</code> property
 * contains the corresponding mouse event.
 *
 * Event: mxEvent.MOVE_END
 *
 * Fires after the window is moved. The <code>event</code> property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.RESIZE_START
 *
 * Fires before the window is resized. The <code>event</code> property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.RESIZE
 *
 * Fires while the window is being resized. The <code>event</code> property
 * contains the corresponding mouse event.
 *
 * Event: mxEvent.RESIZE_END
 *
 * Fires after the window is resized. The <code>event</code> property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.MAXIMIZE
 *
 * Fires after the window is maximized. The <code>event</code> property
 * contains the corresponding mouse event.
 *
 * Event: mxEvent.MINIMIZE
 *
 * Fires after the window is minimized. The <code>event</code> property
 * contains the corresponding mouse event.
 *
 * Event: mxEvent.NORMALIZE
 *
 * Fires after the window is normalized, that is, it returned from
 * maximized or minimized state. The <code>event</code> property contains the
 * corresponding mouse event.
 *
 * Event: mxEvent.ACTIVATE
 *
 * Fires after a window is activated. The <code>previousWindow</code> property
 * contains the previous window. The event sender is the active window.
 *
 * Event: mxEvent.SHOW
 *
 * Fires after the window is shown. This event has no properties.
 *
 * Event: mxEvent.HIDE
 *
 * Fires after the window is hidden. This event has no properties.
 *
 * Event: mxEvent.CLOSE
 *
 * Fires before the window is closed. The <code>event</code> property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.DESTROY
 *
 * Fires before the window is destroyed. This event has no properties.
 *
 * Constructor: mxWindow
 *
 * Constructs a new window with the given dimension and title to display
 * the specified content. The window elements use the given style as a
 * prefix for the classnames of the respective window elements, namely,
 * the window title and window pane. The respective postfixes are appended
 * to the given stylename as follows:
 *
 *   style - Base style for the window.
 *   style+Title - Style for the window title.
 *   style+Pane - Style for the window pane.
 *
 * The default value for style is mxWindow, resulting in the following
 * classnames for the window elements: mxWindow, mxWindowTitle and
 * mxWindowPane.
 *
 * If replaceNode is given then the window replaces the given DOM node in
 * the document.
 *
 * Parameters:
 *
 * title - String that represents the title of the new window.
 * content - DOM node that is used as the window content.
 * x - X-coordinate of the window location.
 * y - Y-coordinate of the window location.
 * width - Width of the window.
 * height - Optional height of the window. Default is to match the height
 * of the content at the specified width.
 * minimizable - Optional boolean indicating if the window is minimizable.
 * Default is true.
 * movable - Optional boolean indicating if the window is movable. Default
 * is true.
 * replaceNode - Optional DOM node that the window should replace.
 * style - Optional base classname for the window elements. Default is
 * mxWindow.
 */
function mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style) {
  if (content != null) {
    minimizable = minimizable != null ? minimizable : true;
    this.content = content;
    this.init(x, y, width, height, style);

    this.installMaximizeHandler();
    this.installMinimizeHandler();
    this.installCloseHandler();
    this.setMinimizable(minimizable);
    this.setTitle(title);

    if (movable == null || movable) {
      this.installMoveHandler();
    }

    if (replaceNode != null && replaceNode.parentNode != null) {
      replaceNode.parentNode.replaceChild(this.div, replaceNode);
    } else {
      // FIXME: 这里无法承载多个编辑器
      // 或许类似于 this.container 会好点
      const parent = document.querySelector('.geEditor') || document.body;
      parent.appendChild(this.div);
    }
  }
}

/**
 * Extends mxEventSource.
 */
mxWindow.prototype = new mxEventSource();
mxWindow.prototype.constructor = mxWindow;

/**
 * Variable: closeImage
 *
 * URL of the image to be used for the close icon in the titlebar.
 */
mxWindow.prototype.closeImage = mxClient.imageBasePath + '/close.gif';

/**
 * Variable: minimizeImage
 *
 * URL of the image to be used for the minimize icon in the titlebar.
 */
mxWindow.prototype.minimizeImage = mxClient.imageBasePath + '/minimize.gif';

/**
 * Variable: normalizeImage
 *
 * URL of the image to be used for the normalize icon in the titlebar.
 */
mxWindow.prototype.normalizeImage = mxClient.imageBasePath + '/normalize.gif';

/**
 * Variable: maximizeImage
 *
 * URL of the image to be used for the maximize icon in the titlebar.
 */
mxWindow.prototype.maximizeImage = mxClient.imageBasePath + '/maximize.gif';

/**
 * Variable: resizeImage
 *
 * URL of the image to be used for the resize icon.
 */
mxWindow.prototype.resizeImage = mxClient.imageBasePath + '/resize.gif';

/**
 * Variable: visible
 *
 * Boolean flag that represents the visible state of the window.
 */
mxWindow.prototype.visible = false;

/**
 * Variable: minimumSize
 *
 * <mxRectangle> that specifies the minimum width and height of the window.
 * Default is (50, 40).
 */
mxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);

/**
 * Variable: destroyOnClose
 *
 * Specifies if the window should be destroyed when it is closed. If this
 * is false then the window is hidden using <setVisible>. Default is true.
 */
mxWindow.prototype.destroyOnClose = true;

/**
 * Variable: contentHeightCorrection
 *
 * Defines the correction factor for computing the height of the contentWrapper.
 * Default is 6 for IE 7/8 standards mode and 2 for all other browsers and modes.
 */
mxWindow.prototype.contentHeightCorrection = document.documentMode == 8 || document.documentMode == 7 ? 6 : 2;

/**
 * Variable: title
 *
 * Reference to the DOM node (TD) that contains the title.
 */
mxWindow.prototype.title = null;

/**
 * Variable: content
 *
 * Reference to the DOM node that represents the window content.
 */
mxWindow.prototype.content = null;

/**
 * Function: init
 *
 * Initializes the DOM tree that represents the window.
 */
mxWindow.prototype.init = function (x, y, width, height, style) {
  style = style != null ? style : 'mxWindow';

  this.div = document.createElement('div');
  this.div.className = style;

  this.div.style.left = x + 'px';
  this.div.style.top = y + 'px';
  this.table = document.createElement('table');
  this.table.className = style;

  // Disables built-in pan and zoom in IE10 and later
  if (mxClient.IS_POINTER) {
    this.div.style.touchAction = 'none';
  }

  // Workaround for table size problems in FF
  if (width != null) {
    if (!mxClient.IS_QUIRKS) {
      this.div.style.width = width + 'px';
    }

    this.table.style.width = width + 'px';
  }

  if (height != null) {
    if (!mxClient.IS_QUIRKS) {
      this.div.style.height = height + 'px';
    }

    this.table.style.height = height + 'px';
  }

  // Creates title row
  var tbody = document.createElement('tbody');
  var tr = document.createElement('tr');

  this.title = document.createElement('td');
  this.title.className = style + 'Title';

  this.buttons = document.createElement('div');
  this.buttons.style.position = 'absolute';
  this.buttons.style.display = 'inline-block';
  this.buttons.style.right = '4px';
  this.buttons.style.top = '5px';
  this.title.appendChild(this.buttons);

  tr.appendChild(this.title);
  tbody.appendChild(tr);

  // Creates content row and table cell
  tr = document.createElement('tr');
  this.td = document.createElement('td');
  this.td.className = style + 'Pane';

  if (document.documentMode == 7) {
    this.td.style.height = '100%';
  }

  this.contentWrapper = document.createElement('div');
  this.contentWrapper.className = style + 'Pane';
  this.contentWrapper.style.width = '100%';
  this.contentWrapper.appendChild(this.content);

  // Workaround for div around div restricts height
  // of inner div if outerdiv has hidden overflow
  if (mxClient.IS_QUIRKS || this.content.nodeName.toUpperCase() != 'DIV') {
    this.contentWrapper.style.height = '100%';
  }

  // Puts all content into the DOM
  this.td.appendChild(this.contentWrapper);
  tr.appendChild(this.td);
  tbody.appendChild(tr);
  this.table.appendChild(tbody);
  this.div.appendChild(this.table);

  // Puts the window on top of other windows when clicked
  var activator = mxUtils.bind(this, function (evt) {
    this.activate();
  });

  mxEvent.addGestureListeners(this.title, activator);
  mxEvent.addGestureListeners(this.table, activator);

  this.hide();
};

/**
 * Function: setTitle
 *
 * Sets the window title to the given string. HTML markup inside the title
 * will be escaped.
 */
mxWindow.prototype.setTitle = function (title) {
  // Removes all text content nodes (normally just one)
  var child = this.title.firstChild;

  while (child != null) {
    var next = child.nextSibling;

    if (child.nodeType == mxConstants.NODETYPE_TEXT) {
      child.parentNode.removeChild(child);
    }

    child = next;
  }

  mxUtils.write(this.title, title || '');
  this.title.appendChild(this.buttons);
};

/**
 * Function: setScrollable
 *
 * Sets if the window contents should be scrollable.
 */
mxWindow.prototype.setScrollable = function (scrollable) {
  // Workaround for hang in Presto 2.5.22 (Opera 10.5)
  if (navigator.userAgent == null || navigator.userAgent.indexOf('Presto/2.5') < 0) {
    if (scrollable) {
      this.contentWrapper.style.overflow = 'auto';
    } else {
      this.contentWrapper.style.overflow = 'hidden';
    }
  }
};

/**
 * Function: activate
 *
 * Puts the window on top of all other windows.
 */
mxWindow.prototype.activate = function () {
  if (mxWindow.activeWindow != this) {
    var style = mxUtils.getCurrentStyle(this.getElement());
    var index = style != null ? style.zIndex : 3;

    if (mxWindow.activeWindow) {
      var elt = mxWindow.activeWindow.getElement();

      if (elt != null && elt.style != null) {
        elt.style.zIndex = index;
      }
    }

    var previousWindow = mxWindow.activeWindow;
    this.getElement().style.zIndex = parseInt(index) + 1;
    mxWindow.activeWindow = this;

    this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow));
  }
};

/**
 * Function: getElement
 *
 * Returuns the outermost DOM node that makes up the window.
 */
mxWindow.prototype.getElement = function () {
  return this.div;
};

/**
 * Function: fit
 *
 * Makes sure the window is inside the client area of the window.
 */
mxWindow.prototype.fit = function () {
  mxUtils.fit(this.div);
};

/**
 * Function: isResizable
 *
 * Returns true if the window is resizable.
 */
mxWindow.prototype.isResizable = function () {
  if (this.resize != null) {
    return this.resize.style.display != 'none';
  }

  return false;
};

/**
 * Function: setResizable
 *
 * Sets if the window should be resizable. To avoid interference with some
 * built-in features of IE10 and later, the use of the following code is
 * recommended if there are resizable <mxWindow>s in the page:
 *
 * (code)
 * if (mxClient.IS_POINTER)
 * {
 *   document.body.style.msTouchAction = 'none';
 * }
 * (end)
 */
mxWindow.prototype.setResizable = function (resizable) {
  if (resizable) {
    if (this.resize == null) {
      this.resize = document.createElement('img');
      this.resize.style.position = 'absolute';
      this.resize.style.bottom = '2px';
      this.resize.style.right = '2px';

      this.resize.setAttribute('src', this.resizeImage);
      this.resize.style.cursor = 'nw-resize';

      var startX = null;
      var startY = null;
      var width = null;
      var height = null;

      var start = mxUtils.bind(this, function (evt) {
        // LATER: pointerdown starting on border of resize does start
        // the drag operation but does not fire consecutive events via
        // one of the listeners below (does pan instead).
        // Workaround: document.body.style.msTouchAction = 'none'
        this.activate();
        startX = mxEvent.getClientX(evt);
        startY = mxEvent.getClientY(evt);
        width = this.div.offsetWidth;
        height = this.div.offsetHeight;

        mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
        this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt));
        mxEvent.consume(evt);
      });

      // Adds a temporary pair of listeners to intercept
      // the gesture event in the document
      var dragHandler = mxUtils.bind(this, function (evt) {
        if (startX != null && startY != null) {
          var dx = mxEvent.getClientX(evt) - startX;
          var dy = mxEvent.getClientY(evt) - startY;

          this.setSize(width + dx, height + dy);

          this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt));
          mxEvent.consume(evt);
        }
      });

      var dropHandler = mxUtils.bind(this, function (evt) {
        if (startX != null && startY != null) {
          startX = null;
          startY = null;
          mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
          this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt));
          mxEvent.consume(evt);
        }
      });

      mxEvent.addGestureListeners(this.resize, start, dragHandler, dropHandler);
      this.div.appendChild(this.resize);
    } else {
      this.resize.style.display = 'inline';
    }
  } else if (this.resize != null) {
    this.resize.style.display = 'none';
  }
};

/**
 * Function: setSize
 *
 * Sets the size of the window.
 */
mxWindow.prototype.setSize = function (width, height) {
  width = Math.max(this.minimumSize.width, width);
  height = Math.max(this.minimumSize.height, height);

  // Workaround for table size problems in FF
  if (!mxClient.IS_QUIRKS) {
    this.div.style.width = width + 'px';
    this.div.style.height = height + 'px';
  }

  this.table.style.width = width + 'px';
  this.table.style.height = height + 'px';

  if (!mxClient.IS_QUIRKS) {
    this.contentWrapper.style.height =
      this.div.offsetHeight - this.title.offsetHeight - this.contentHeightCorrection + 'px';
  }
};

/**
 * Function: setMinimizable
 *
 * Sets if the window is minimizable.
 */
mxWindow.prototype.setMinimizable = function (minimizable) {
  this.minimize.style.display = minimizable ? '' : 'none';
};

/**
 * Function: getMinimumSize
 *
 * Returns an <mxRectangle> that specifies the size for the minimized window.
 * A width or height of 0 means keep the existing width or height. This
 * implementation returns the height of the window title and keeps the width.
 */
mxWindow.prototype.getMinimumSize = function () {
  return new mxRectangle(0, 0, 0, this.title.offsetHeight);
};

/**
 * Function: installMinimizeHandler
 *
 * Installs the event listeners required for minimizing the window.
 */
mxWindow.prototype.installMinimizeHandler = function () {
  this.minimize = document.createElement('img');

  this.minimize.setAttribute('src', this.minimizeImage);
  this.minimize.setAttribute('title', 'Minimize');
  this.minimize.style.cursor = 'pointer';
  this.minimize.style.marginLeft = '2px';
  this.minimize.style.display = 'none';

  this.buttons.appendChild(this.minimize);

  var minimized = false;
  var maxDisplay = null;
  var height = null;

  var funct = mxUtils.bind(this, function (evt) {
    this.activate();

    if (!minimized) {
      minimized = true;

      this.minimize.setAttribute('src', this.normalizeImage);
      this.minimize.setAttribute('title', 'Normalize');
      this.contentWrapper.style.display = 'none';
      maxDisplay = this.maximize.style.display;

      this.maximize.style.display = 'none';
      height = this.table.style.height;

      var minSize = this.getMinimumSize();

      if (minSize.height > 0) {
        if (!mxClient.IS_QUIRKS) {
          this.div.style.height = minSize.height + 'px';
        }

        this.table.style.height = minSize.height + 'px';
      }

      if (minSize.width > 0) {
        if (!mxClient.IS_QUIRKS) {
          this.div.style.width = minSize.width + 'px';
        }

        this.table.style.width = minSize.width + 'px';
      }

      if (this.resize != null) {
        this.resize.style.visibility = 'hidden';
      }

      this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt));
    } else {
      minimized = false;

      this.minimize.setAttribute('src', this.minimizeImage);
      this.minimize.setAttribute('title', 'Minimize');
      this.contentWrapper.style.display = ''; // default
      this.maximize.style.display = maxDisplay;

      if (!mxClient.IS_QUIRKS) {
        this.div.style.height = height;
      }

      this.table.style.height = height;

      if (this.resize != null) {
        this.resize.style.visibility = '';
      }

      this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
    }

    mxEvent.consume(evt);
  });

  mxEvent.addGestureListeners(this.minimize, funct);
};

/**
 * Function: setMaximizable
 *
 * Sets if the window is maximizable.
 */
mxWindow.prototype.setMaximizable = function (maximizable) {
  this.maximize.style.display = maximizable ? '' : 'none';
};

/**
 * Function: installMaximizeHandler
 *
 * Installs the event listeners required for maximizing the window.
 */
mxWindow.prototype.installMaximizeHandler = function () {
  this.maximize = document.createElement('img');

  this.maximize.setAttribute('src', this.maximizeImage);
  this.maximize.setAttribute('title', 'Maximize');
  this.maximize.style.cursor = 'default';
  this.maximize.style.marginLeft = '2px';
  this.maximize.style.cursor = 'pointer';
  this.maximize.style.display = 'none';

  this.buttons.appendChild(this.maximize);

  var maximized = false;
  var x = null;
  var y = null;
  var height = null;
  var width = null;
  var minDisplay = null;

  var funct = mxUtils.bind(this, function (evt) {
    this.activate();

    if (this.maximize.style.display != 'none') {
      if (!maximized) {
        maximized = true;

        this.maximize.setAttribute('src', this.normalizeImage);
        this.maximize.setAttribute('title', 'Normalize');
        this.contentWrapper.style.display = '';
        minDisplay = this.minimize.style.display;
        this.minimize.style.display = 'none';

        // Saves window state
        x = parseInt(this.div.style.left);
        y = parseInt(this.div.style.top);
        height = this.table.style.height;
        width = this.table.style.width;

        this.div.style.left = '0px';
        this.div.style.top = '0px';
        var docHeight = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0);

        if (!mxClient.IS_QUIRKS) {
          this.div.style.width = document.body.clientWidth - 2 + 'px';
          this.div.style.height = docHeight - 2 + 'px';
        }

        this.table.style.width = document.body.clientWidth - 2 + 'px';
        this.table.style.height = docHeight - 2 + 'px';

        if (this.resize != null) {
          this.resize.style.visibility = 'hidden';
        }

        if (!mxClient.IS_QUIRKS) {
          var style = mxUtils.getCurrentStyle(this.contentWrapper);

          if (style.overflow == 'auto' || this.resize != null) {
            this.contentWrapper.style.height =
              this.div.offsetHeight - this.title.offsetHeight - this.contentHeightCorrection + 'px';
          }
        }

        this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt));
      } else {
        maximized = false;

        this.maximize.setAttribute('src', this.maximizeImage);
        this.maximize.setAttribute('title', 'Maximize');
        this.contentWrapper.style.display = '';
        this.minimize.style.display = minDisplay;

        // Restores window state
        this.div.style.left = x + 'px';
        this.div.style.top = y + 'px';

        if (!mxClient.IS_QUIRKS) {
          this.div.style.height = height;
          this.div.style.width = width;

          var style = mxUtils.getCurrentStyle(this.contentWrapper);

          if (style.overflow == 'auto' || this.resize != null) {
            this.contentWrapper.style.height =
              this.div.offsetHeight - this.title.offsetHeight - this.contentHeightCorrection + 'px';
          }
        }

        this.table.style.height = height;
        this.table.style.width = width;

        if (this.resize != null) {
          this.resize.style.visibility = '';
        }

        this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
      }

      mxEvent.consume(evt);
    }
  });

  mxEvent.addGestureListeners(this.maximize, funct);
  mxEvent.addListener(this.title, 'dblclick', funct);
};

/**
 * Function: installMoveHandler
 *
 * Installs the event listeners required for moving the window.
 */
mxWindow.prototype.installMoveHandler = function () {
  this.title.style.cursor = 'move';

  mxEvent.addGestureListeners(
    this.title,
    mxUtils.bind(this, function (evt) {
      var startX = mxEvent.getClientX(evt);
      var startY = mxEvent.getClientY(evt);
      var x = this.getX();
      var y = this.getY();

      // Adds a temporary pair of listeners to intercept
      // the gesture event in the document
      var dragHandler = mxUtils.bind(this, function (evt) {
        var dx = mxEvent.getClientX(evt) - startX;
        var dy = mxEvent.getClientY(evt) - startY;
        this.setLocation(x + dx, y + dy);
        this.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt));
        mxEvent.consume(evt);
      });

      var dropHandler = mxUtils.bind(this, function (evt) {
        mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
        this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt));
        mxEvent.consume(evt);
      });

      mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
      this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt));
      mxEvent.consume(evt);
    })
  );

  // Disables built-in pan and zoom in IE10 and later
  if (mxClient.IS_POINTER) {
    this.title.style.touchAction = 'none';
  }
};

/**
 * Function: setLocation
 *
 * Sets the upper, left corner of the window.
 */
mxWindow.prototype.setLocation = function (x, y) {
  this.div.style.left = x + 'px';
  this.div.style.top = y + 'px';
};

/**
 * Function: getX
 *
 * Returns the current position on the x-axis.
 */
mxWindow.prototype.getX = function () {
  return parseInt(this.div.style.left);
};

/**
 * Function: getY
 *
 * Returns the current position on the y-axis.
 */
mxWindow.prototype.getY = function () {
  return parseInt(this.div.style.top);
};

/**
 * Function: installCloseHandler
 *
 * Adds the <closeImage> as a new image node in <closeImg> and installs the
 * <close> event.
 */
mxWindow.prototype.installCloseHandler = function () {
  this.closeImg = document.createElement('img');

  this.closeImg.setAttribute('src', this.closeImage);
  this.closeImg.setAttribute('title', 'Close');
  this.closeImg.style.marginLeft = '2px';
  this.closeImg.style.cursor = 'pointer';
  this.closeImg.style.display = 'none';

  this.buttons.appendChild(this.closeImg);

  mxEvent.addGestureListeners(
    this.closeImg,
    mxUtils.bind(this, function (evt) {
      this.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt));

      if (this.destroyOnClose) {
        this.destroy();
      } else {
        this.setVisible(false);
      }

      mxEvent.consume(evt);
    })
  );
};

/**
 * Function: setImage
 *
 * Sets the image associated with the window.
 *
 * Parameters:
 *
 * image - URL of the image to be used.
 */
mxWindow.prototype.setImage = function (image) {
  this.image = document.createElement('img');
  this.image.setAttribute('src', image);
  this.image.setAttribute('align', 'left');
  this.image.style.marginRight = '4px';
  this.image.style.marginLeft = '0px';
  this.image.style.marginTop = '-2px';

  this.title.insertBefore(this.image, this.title.firstChild);
};

/**
 * Function: setClosable
 *
 * Sets the image associated with the window.
 *
 * Parameters:
 *
 * closable - Boolean specifying if the window should be closable.
 */
mxWindow.prototype.setClosable = function (closable) {
  this.closeImg.style.display = closable ? '' : 'none';
};

/**
 * Function: isVisible
 *
 * Returns true if the window is visible.
 */
mxWindow.prototype.isVisible = function () {
  if (this.div != null) {
    return this.div.style.display != 'none';
  }

  return false;
};

/**
 * Function: setVisible
 *
 * Shows or hides the window depending on the given flag.
 *
 * Parameters:
 *
 * visible - Boolean indicating if the window should be made visible.
 */
mxWindow.prototype.setVisible = function (visible) {
  if (this.div != null && this.isVisible() != visible) {
    if (visible) {
      this.show();
    } else {
      this.hide();
    }
  }
};

/**
 * Function: show
 *
 * Shows the window.
 */
mxWindow.prototype.show = function () {
  this.div.style.display = '';
  this.activate();

  var style = mxUtils.getCurrentStyle(this.contentWrapper);

  if (
    !mxClient.IS_QUIRKS &&
    (style.overflow == 'auto' || this.resize != null) &&
    this.contentWrapper.style.display != 'none'
  ) {
    this.contentWrapper.style.height =
      this.div.offsetHeight - this.title.offsetHeight - this.contentHeightCorrection + 'px';
  }

  this.fireEvent(new mxEventObject(mxEvent.SHOW));
};

/**
 * Function: hide
 *
 * Hides the window.
 */
mxWindow.prototype.hide = function () {
  this.div.style.display = 'none';
  this.fireEvent(new mxEventObject(mxEvent.HIDE));
};

/**
 * Function: destroy
 *
 * Destroys the window and removes all associated resources. Fires a
 * <destroy> event prior to destroying the window.
 */
mxWindow.prototype.destroy = function () {
  this.fireEvent(new mxEventObject(mxEvent.DESTROY));

  if (this.div != null) {
    mxEvent.release(this.div);
    this.div.parentNode.removeChild(this.div);
    this.div = null;
  }

  this.title = null;
  this.content = null;
  this.contentWrapper = null;
};

/**
 * util/mxForm.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxForm
 *
 * A simple class for creating HTML forms.
 *
 * Constructor: mxForm
 *
 * Creates a HTML table using the specified classname.
 */
function mxForm(className) {
  this.table = document.createElement('table');
  this.table.className = className;
  this.body = document.createElement('tbody');

  this.table.appendChild(this.body);
}

/**
 * Variable: table
 *
 * Holds the DOM node that represents the table.
 */
mxForm.prototype.table = null;

/**
 * Variable: body
 *
 * Holds the DOM node that represents the tbody (table body). New rows
 * can be added to this object using DOM API.
 */
mxForm.prototype.body = false;

/**
 * Function: getTable
 *
 * Returns the table that contains this form.
 */
mxForm.prototype.getTable = function () {
  return this.table;
};

/**
 * Function: addButtons
 *
 * Helper method to add an OK and Cancel button using the respective
 * functions.
 */
mxForm.prototype.addButtons = function (okFunct, cancelFunct) {
  var tr = document.createElement('tr');
  var td = document.createElement('td');
  tr.appendChild(td);
  td = document.createElement('td');

  // Adds the ok button
  var button = document.createElement('button');
  mxUtils.write(button, mxResources.get('ok') || 'OK');
  td.appendChild(button);

  mxEvent.addListener(button, 'click', function () {
    okFunct();
  });

  // Adds the cancel button
  button = document.createElement('button');
  mxUtils.write(button, mxResources.get('cancel') || 'Cancel');
  td.appendChild(button);

  mxEvent.addListener(button, 'click', function () {
    cancelFunct();
  });

  tr.appendChild(td);
  this.body.appendChild(tr);
};

/**
 * Function: addText
 *
 * Adds an input for the given name, type and value and returns it.
 */
mxForm.prototype.addText = function (name, value, type) {
  var input = document.createElement('input');

  input.setAttribute('type', type || 'text');
  input.value = value;

  return this.addField(name, input);
};

/**
 * Function: addCheckbox
 *
 * Adds a checkbox for the given name and value and returns the textfield.
 */
mxForm.prototype.addCheckbox = function (name, value) {
  var input = document.createElement('input');

  input.setAttribute('type', 'checkbox');
  this.addField(name, input);

  // IE can only change the checked value if the input is inside the DOM
  if (value) {
    input.checked = true;
  }

  return input;
};

/**
 * Function: addTextarea
 *
 * Adds a textarea for the given name and value and returns the textarea.
 */
mxForm.prototype.addTextarea = function (name, value, rows) {
  var input = document.createElement('textarea');

  if (mxClient.IS_NS) {
    rows--;
  }

  input.setAttribute('rows', rows || 2);
  input.value = value;

  return this.addField(name, input);
};

/**
 * Function: addCombo
 *
 * Adds a combo for the given name and returns the combo.
 */
mxForm.prototype.addCombo = function (name, isMultiSelect, size) {
  var select = document.createElement('select');

  if (size != null) {
    select.setAttribute('size', size);
  }

  if (isMultiSelect) {
    select.setAttribute('multiple', 'true');
  }

  return this.addField(name, select);
};

/**
 * Function: addOption
 *
 * Adds an option for the given label to the specified combo.
 */
mxForm.prototype.addOption = function (combo, label, value, isSelected) {
  var option = document.createElement('option');

  mxUtils.writeln(option, label);
  option.setAttribute('value', value);

  if (isSelected) {
    option.setAttribute('selected', isSelected);
  }

  combo.appendChild(option);
};

/**
 * Function: addField
 *
 * Adds a new row with the name and the input field in two columns and
 * returns the given input.
 */
mxForm.prototype.addField = function (name, input) {
  var tr = document.createElement('tr');
  var td = document.createElement('td');
  mxUtils.write(td, name);
  tr.appendChild(td);

  td = document.createElement('td');
  td.appendChild(input);
  tr.appendChild(td);
  this.body.appendChild(tr);

  return input;
};

/**
 * util/mxImage.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxImage
 *
 * Encapsulates the URL, width and height of an image.
 *
 * Constructor: mxImage
 *
 * Constructs a new image.
 */
function mxImage(src, width, height) {
  this.src = src;
  this.width = width;
  this.height = height;
}

/**
 * Variable: src
 *
 * String that specifies the URL of the image.
 */
mxImage.prototype.src = null;

/**
 * Variable: width
 *
 * Integer that specifies the width of the image.
 */
mxImage.prototype.width = null;

/**
 * Variable: height
 *
 * Integer that specifies the height of the image.
 */
mxImage.prototype.height = null;

/**
 * util/mxDivResizer.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDivResizer
 *
 * Maintains the size of a div element in Internet Explorer. This is a
 * workaround for the right and bottom style being ignored in IE.
 *
 * If you need a div to cover the scrollwidth and -height of a document,
 * then you can use this class as follows:
 *
 * (code)
 * var resizer = new mxDivResizer(background);
 * resizer.getDocumentHeight = function()
 * {
 *   return document.body.scrollHeight;
 * }
 * resizer.getDocumentWidth = function()
 * {
 *   return document.body.scrollWidth;
 * }
 * resizer.resize();
 * (end)
 *
 * Constructor: mxDivResizer
 *
 * Constructs an object that maintains the size of a div
 * element when the window is being resized. This is only
 * required for Internet Explorer as it ignores the respective
 * stylesheet information for DIV elements.
 *
 * Parameters:
 *
 * div - Reference to the DOM node whose size should be maintained.
 * container - Optional Container that contains the div. Default is the
 * window.
 */
function mxDivResizer(div, container) {
  if (div.nodeName.toLowerCase() == 'div') {
    if (container == null) {
      container = window;
    }

    this.div = div;
    var style = mxUtils.getCurrentStyle(div);

    if (style != null) {
      this.resizeWidth = style.width == 'auto';
      this.resizeHeight = style.height == 'auto';
    }

    mxEvent.addListener(
      container,
      'resize',
      mxUtils.bind(this, function (evt) {
        if (!this.handlingResize) {
          this.handlingResize = true;
          this.resize();
          this.handlingResize = false;
        }
      })
    );

    this.resize();
  }
}

/**
 * Function: resizeWidth
 *
 * Boolean specifying if the width should be updated.
 */
mxDivResizer.prototype.resizeWidth = true;

/**
 * Function: resizeHeight
 *
 * Boolean specifying if the height should be updated.
 */
mxDivResizer.prototype.resizeHeight = true;

/**
 * Function: handlingResize
 *
 * Boolean specifying if the width should be updated.
 */
mxDivResizer.prototype.handlingResize = false;

/**
 * Function: resize
 *
 * Updates the style of the DIV after the window has been resized.
 */
mxDivResizer.prototype.resize = function () {
  var w = this.getDocumentWidth();
  var h = this.getDocumentHeight();

  var l = parseInt(this.div.style.left);
  var r = parseInt(this.div.style.right);
  var t = parseInt(this.div.style.top);
  var b = parseInt(this.div.style.bottom);

  if (this.resizeWidth && !isNaN(l) && !isNaN(r) && l >= 0 && r >= 0 && w - r - l > 0) {
    this.div.style.width = w - r - l + 'px';
  }

  if (this.resizeHeight && !isNaN(t) && !isNaN(b) && t >= 0 && b >= 0 && h - t - b > 0) {
    this.div.style.height = h - t - b + 'px';
  }
};

/**
 * Function: getDocumentWidth
 *
 * Hook for subclassers to return the width of the document (without
 * scrollbars).
 */
mxDivResizer.prototype.getDocumentWidth = function () {
  return document.body.clientWidth;
};

/**
 * Function: getDocumentHeight
 *
 * Hook for subclassers to return the height of the document (without
 * scrollbars).
 */
mxDivResizer.prototype.getDocumentHeight = function () {
  return document.body.clientHeight;
};

/**
 * util/mxDragSource.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDragSource
 *
 * Wrapper to create a drag source from a DOM element so that the element can
 * be dragged over a graph and dropped into the graph as a new cell.
 *
 * Problem is that in the dropHandler the current preview location is not
 * available, so the preview and the dropHandler must match.
 *
 * Constructor: mxDragSource
 *
 * Constructs a new drag source for the given element.
 */
function mxDragSource(element, dropHandler) {
  this.element = element;
  this.dropHandler = dropHandler;

  // Handles a drag gesture on the element
  mxEvent.addGestureListeners(
    element,
    mxUtils.bind(this, function (evt) {
      this.mouseDown(evt);
    })
  );

  // Prevents native drag and drop
  mxEvent.addListener(element, 'dragstart', function (evt) {
    mxEvent.consume(evt);
  });

  this.eventConsumer = function (sender, evt) {
    var evtName = evt.getProperty('eventName');
    var me = evt.getProperty('event');

    if (evtName != mxEvent.MOUSE_DOWN) {
      me.consume();
    }
  };
}

/**
 * Variable: element
 *
 * Reference to the DOM node which was made draggable.
 */
mxDragSource.prototype.element = null;

/**
 * Variable: dropHandler
 *
 * Holds the DOM node that is used to represent the drag preview. If this is
 * null then the source element will be cloned and used for the drag preview.
 */
mxDragSource.prototype.dropHandler = null;

/**
 * Variable: dragOffset
 *
 * <mxPoint> that specifies the offset of the <dragElement>. Default is null.
 */
mxDragSource.prototype.dragOffset = null;

/**
 * Variable: dragElement
 *
 * Holds the DOM node that is used to represent the drag preview. If this is
 * null then the source element will be cloned and used for the drag preview.
 */
mxDragSource.prototype.dragElement = null;

/**
 * Variable: previewElement
 *
 * Optional <mxRectangle> that specifies the unscaled size of the preview.
 */
mxDragSource.prototype.previewElement = null;

/**
 * Variable: previewOffset
 *
 * Optional <mxPoint> that specifies the offset of the preview in pixels.
 */
mxDragSource.prototype.previewOffset = null;

/**
 * Variable: enabled
 *
 * Specifies if this drag source is enabled. Default is true.
 */
mxDragSource.prototype.enabled = true;

/**
 * Variable: currentGraph
 *
 * Reference to the <mxGraph> that is the current drop target.
 */
mxDragSource.prototype.currentGraph = null;

/**
 * Variable: currentDropTarget
 *
 * Holds the current drop target under the mouse.
 */
mxDragSource.prototype.currentDropTarget = null;

/**
 * Variable: currentPoint
 *
 * Holds the current drop location.
 */
mxDragSource.prototype.currentPoint = null;

/**
 * Variable: currentGuide
 *
 * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
 */
mxDragSource.prototype.currentGuide = null;

/**
 * Variable: currentGuide
 *
 * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
 */
mxDragSource.prototype.currentHighlight = null;

/**
 * Variable: autoscroll
 *
 * Specifies if the graph should scroll automatically. Default is true.
 */
mxDragSource.prototype.autoscroll = true;

/**
 * Variable: guidesEnabled
 *
 * Specifies if <mxGuide> should be enabled. Default is true.
 */
mxDragSource.prototype.guidesEnabled = true;

/**
 * Variable: gridEnabled
 *
 * Specifies if the grid should be allowed. Default is true.
 */
mxDragSource.prototype.gridEnabled = true;

/**
 * Variable: highlightDropTargets
 *
 * Specifies if drop targets should be highlighted. Default is true.
 */
mxDragSource.prototype.highlightDropTargets = true;

/**
 * Variable: dragElementZIndex
 *
 * ZIndex for the drag element. Default is 100.
 */
mxDragSource.prototype.dragElementZIndex = 100;

/**
 * Variable: dragElementOpacity
 *
 * Opacity of the drag element in %. Default is 70.
 */
mxDragSource.prototype.dragElementOpacity = 70;

/**
 * Variable: checkEventSource
 *
 * Whether the event source should be checked in <graphContainerEvent>. Default
 * is true.
 */
mxDragSource.prototype.checkEventSource = true;

/**
 * Function: isEnabled
 *
 * Returns <enabled>.
 */
mxDragSource.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Sets <enabled>.
 */
mxDragSource.prototype.setEnabled = function (value) {
  this.enabled = value;
};

/**
 * Function: isGuidesEnabled
 *
 * Returns <guidesEnabled>.
 */
mxDragSource.prototype.isGuidesEnabled = function () {
  return this.guidesEnabled;
};

/**
 * Function: setGuidesEnabled
 *
 * Sets <guidesEnabled>.
 */
mxDragSource.prototype.setGuidesEnabled = function (value) {
  this.guidesEnabled = value;
};

/**
 * Function: isGridEnabled
 *
 * Returns <gridEnabled>.
 */
mxDragSource.prototype.isGridEnabled = function () {
  return this.gridEnabled;
};

/**
 * Function: setGridEnabled
 *
 * Sets <gridEnabled>.
 */
mxDragSource.prototype.setGridEnabled = function (value) {
  this.gridEnabled = value;
};

/**
 * Function: getGraphForEvent
 *
 * Returns the graph for the given mouse event. This implementation returns
 * null.
 */
mxDragSource.prototype.getGraphForEvent = function (evt) {
  return null;
};

/**
 * Function: getDropTarget
 *
 * Returns the drop target for the given graph and coordinates. This
 * implementation uses <mxGraph.getCellAt>.
 */
mxDragSource.prototype.getDropTarget = function (graph, x, y, evt) {
  return graph.getCellAt(x, y);
};

/**
 * Function: createDragElement
 *
 * Creates and returns a clone of the <dragElementPrototype> or the <element>
 * if the former is not defined.
 */
mxDragSource.prototype.createDragElement = function (evt) {
  return this.element.cloneNode(true);
};

/**
 * Function: createPreviewElement
 *
 * Creates and returns an element which can be used as a preview in the given
 * graph.
 */
mxDragSource.prototype.createPreviewElement = function (graph) {
  return null;
};

/**
 * Function: isActive
 *
 * Returns true if this drag source is active.
 */
mxDragSource.prototype.isActive = function () {
  return this.mouseMoveHandler != null;
};

/**
 * Function: reset
 *
 * Stops and removes everything and restores the state of the object.
 */
mxDragSource.prototype.reset = function () {
  if (this.currentGraph != null) {
    this.dragExit(this.currentGraph);
    this.currentGraph = null;
  }

  this.removeDragElement();
  this.removeListeners();
  this.stopDrag();
};

/**
 * Function: mouseDown
 *
 * Returns the drop target for the given graph and coordinates. This
 * implementation uses <mxGraph.getCellAt>.
 *
 * To ignore popup menu events for a drag source, this function can be
 * overridden as follows.
 *
 * (code)
 * var mouseDown = dragSource.mouseDown;
 *
 * dragSource.mouseDown = function(evt)
 * {
 *   if (!mxEvent.isPopupTrigger(evt))
 *   {
 *     mouseDown.apply(this, arguments);
 *   }
 * };
 * (end)
 */
mxDragSource.prototype.mouseDown = function (evt) {
  if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null) {
    this.startDrag(evt);
    this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove);
    this.mouseUpHandler = mxUtils.bind(this, this.mouseUp);
    mxEvent.addGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);

    if (mxClient.IS_TOUCH && !mxEvent.isMouseEvent(evt)) {
      this.eventSource = mxEvent.getSource(evt);
      mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
    }
  }
};

/**
 * Function: startDrag
 *
 * Creates the <dragElement> using <createDragElement>.
 */
mxDragSource.prototype.startDrag = function (evt) {
  this.dragElement = this.createDragElement(evt);
  this.dragElement.style.position = 'absolute';
  this.dragElement.style.zIndex = this.dragElementZIndex;
  mxUtils.setOpacity(this.dragElement, this.dragElementOpacity);

  if (this.checkEventSource && mxClient.IS_SVG) {
    this.dragElement.style.pointerEvents = 'none';
  }
};

/**
 * Function: stopDrag
 *
 * Invokes <removeDragElement>.
 */
mxDragSource.prototype.stopDrag = function () {
  // LATER: This used to have a mouse event. If that is still needed we need to add another
  // final call to the DnD protocol to add a cleanup step in the case of escape press, which
  // is not associated with a mouse event and which currently calles this method.
  this.removeDragElement();
};

/**
 * Function: removeDragElement
 *
 * Removes and destroys the <dragElement>.
 */
mxDragSource.prototype.removeDragElement = function () {
  if (this.dragElement != null) {
    if (this.dragElement.parentNode != null) {
      this.dragElement.parentNode.removeChild(this.dragElement);
    }

    this.dragElement = null;
  }
};

/**
 * Function: getElementForEvent
 *
 * Returns the topmost element under the given event.
 */
mxDragSource.prototype.getElementForEvent = function (evt) {
  return mxEvent.isTouchEvent(evt) || mxEvent.isPenEvent(evt)
    ? document.elementFromPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt))
    : mxEvent.getSource(evt);
};

/**
 * Function: graphContainsEvent
 *
 * Returns true if the given graph contains the given event.
 */
mxDragSource.prototype.graphContainsEvent = function (graph, evt) {
  var x = mxEvent.getClientX(evt);
  var y = mxEvent.getClientY(evt);
  var offset = mxUtils.getOffset(graph.container);
  var origin = mxUtils.getScrollOrigin();
  var elt = this.getElementForEvent(evt);

  if (this.checkEventSource) {
    while (elt != null && elt != graph.container) {
      elt = elt.parentNode;
    }
  }

  // Checks if event is inside the bounds of the graph container
  return (
    elt != null &&
    x >= offset.x - origin.x &&
    y >= offset.y - origin.y &&
    x <= offset.x - origin.x + graph.container.offsetWidth &&
    y <= offset.y - origin.y + graph.container.offsetHeight
  );
};

/**
 * Function: mouseMove
 *
 * Gets the graph for the given event using <getGraphForEvent>, updates the
 * <currentGraph>, calling <dragEnter> and <dragExit> on the new and old graph,
 * respectively, and invokes <dragOver> if <currentGraph> is not null.
 */
mxDragSource.prototype.mouseMove = function (evt) {
  var graph = this.getGraphForEvent(evt);

  // Checks if event is inside the bounds of the graph container
  if (graph != null && !this.graphContainsEvent(graph, evt)) {
    graph = null;
  }

  if (graph != this.currentGraph) {
    if (this.currentGraph != null) {
      this.dragExit(this.currentGraph, evt);
    }

    this.currentGraph = graph;

    if (this.currentGraph != null) {
      this.dragEnter(this.currentGraph, evt);
    }
  }

  if (this.currentGraph != null) {
    this.dragOver(this.currentGraph, evt);
  }

  if (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible')) {
    var x = mxEvent.getClientX(evt);
    var y = mxEvent.getClientY(evt);

    if (this.dragElement.parentNode == null) {
      document.body.appendChild(this.dragElement);
    }

    this.dragElement.style.visibility = 'visible';

    if (this.dragOffset != null) {
      x += this.dragOffset.x;
      y += this.dragOffset.y;
    }

    var offset = mxUtils.getDocumentScrollOrigin(document);

    this.dragElement.style.left = x + offset.x + 'px';
    this.dragElement.style.top = y + offset.y + 'px';
  } else if (this.dragElement != null) {
    this.dragElement.style.visibility = 'hidden';
  }

  mxEvent.consume(evt);
};

/**
 * Function: mouseUp
 *
 * Processes the mouse up event and invokes <drop>, <dragExit> and <stopDrag>
 * as required.
 */
mxDragSource.prototype.mouseUp = function (evt) {
  if (this.currentGraph != null) {
    if (
      this.currentPoint != null &&
      (this.previewElement == null || this.previewElement.style.visibility != 'hidden')
    ) {
      var scale = this.currentGraph.view.scale;
      var tr = this.currentGraph.view.translate;
      var x = this.currentPoint.x / scale - tr.x;
      var y = this.currentPoint.y / scale - tr.y;

      this.drop(this.currentGraph, evt, this.currentDropTarget, x, y);
    }

    this.dragExit(this.currentGraph);
    this.currentGraph = null;
  }

  this.stopDrag();
  this.removeListeners();

  mxEvent.consume(evt);
};

/**
 * Function: removeListeners
 *
 * Actives the given graph as a drop target.
 */
mxDragSource.prototype.removeListeners = function () {
  if (this.eventSource != null) {
    mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
    this.eventSource = null;
  }

  mxEvent.removeGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
  this.mouseMoveHandler = null;
  this.mouseUpHandler = null;
};

/**
 * Function: dragEnter
 *
 * Actives the given graph as a drop target.
 */
mxDragSource.prototype.dragEnter = function (graph, evt) {
  graph.isMouseDown = true;
  graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
  this.previewElement = this.createPreviewElement(graph);

  if (this.previewElement != null && this.checkEventSource && mxClient.IS_SVG) {
    this.previewElement.style.pointerEvents = 'none';
  }

  // Guide is only needed if preview element is used
  if (this.isGuidesEnabled() && this.previewElement != null) {
    this.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates());
  }

  if (this.highlightDropTargets) {
    this.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR);
  }

  // Consumes all events in the current graph before they are fired
  graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.eventConsumer);
};

/**
 * Function: dragExit
 *
 * Deactivates the given graph as a drop target.
 */
mxDragSource.prototype.dragExit = function (graph, evt) {
  this.currentDropTarget = null;
  this.currentPoint = null;
  graph.isMouseDown = false;

  // Consumes all events in the current graph before they are fired
  graph.removeListener(this.eventConsumer);

  if (this.previewElement != null) {
    if (this.previewElement.parentNode != null) {
      this.previewElement.parentNode.removeChild(this.previewElement);
    }

    this.previewElement = null;
  }

  if (this.currentGuide != null) {
    this.currentGuide.destroy();
    this.currentGuide = null;
  }

  if (this.currentHighlight != null) {
    this.currentHighlight.destroy();
    this.currentHighlight = null;
  }
};

/**
 * Function: dragOver
 *
 * Implements autoscroll, updates the <currentPoint>, highlights any drop
 * targets and updates the preview.
 */
mxDragSource.prototype.dragOver = function (graph, evt) {
  var offset = mxUtils.getOffset(graph.container);
  var origin = mxUtils.getScrollOrigin(graph.container);
  var x = mxEvent.getClientX(evt) - offset.x + origin.x - graph.panDx;
  var y = mxEvent.getClientY(evt) - offset.y + origin.y - graph.panDy;

  if (graph.autoScroll && (this.autoscroll == null || this.autoscroll)) {
    graph.scrollPointToVisible(x, y, graph.autoExtend);
  }

  // Highlights the drop target under the mouse
  if (this.currentHighlight != null && graph.isDropEnabled()) {
    this.currentDropTarget = this.getDropTarget(graph, x, y, evt);
    var state = graph.getView().getState(this.currentDropTarget);
    this.currentHighlight.highlight(state);
  }

  // Updates the location of the preview
  if (this.previewElement != null) {
    if (this.previewElement.parentNode == null) {
      graph.container.appendChild(this.previewElement);

      this.previewElement.style.zIndex = '3';
      this.previewElement.style.position = 'absolute';
    }

    var gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt);
    var hideGuide = true;

    // Grid and guides
    if (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt)) {
      // LATER: HTML preview appears smaller than SVG preview
      var w = parseInt(this.previewElement.style.width);
      var h = parseInt(this.previewElement.style.height);
      var bounds = new mxRectangle(0, 0, w, h);
      var delta = new mxPoint(x, y);
      delta = this.currentGuide.move(bounds, delta, gridEnabled, true);
      hideGuide = false;
      x = delta.x;
      y = delta.y;
    } else if (gridEnabled) {
      var scale = graph.view.scale;
      var tr = graph.view.translate;
      var off = graph.gridSize / 2;
      x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;
      y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;
    }

    if (this.currentGuide != null && hideGuide) {
      this.currentGuide.hide();
    }

    if (this.previewOffset != null) {
      x += this.previewOffset.x;
      y += this.previewOffset.y;
    }

    this.previewElement.style.left = Math.round(x) + 'px';
    this.previewElement.style.top = Math.round(y) + 'px';
    this.previewElement.style.visibility = 'visible';
  }

  this.currentPoint = new mxPoint(x, y);
};

/**
 * Function: drop
 *
 * Returns the drop target for the given graph and coordinates. This
 * implementation uses <mxGraph.getCellAt>.
 */
mxDragSource.prototype.drop = function (graph, evt, dropTarget, x, y) {
  this.dropHandler.apply(this, arguments);

  // Had to move this to after the insert because it will
  // affect the scrollbars of the window in IE to try and
  // make the complete container visible.
  // LATER: Should be made optional.
  if (graph.container.style.visibility != 'hidden') {
    graph.container.focus();
  }
};

/**
 * util/mxToolbar.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxToolbar
 *
 * Creates a toolbar inside a given DOM node. The toolbar may contain icons,
 * buttons and combo boxes.
 *
 * Event: mxEvent.SELECT
 *
 * Fires when an item was selected in the toolbar. The <code>function</code>
 * property contains the function that was selected in <selectMode>.
 *
 * Constructor: mxToolbar
 *
 * Constructs a toolbar in the specified container.
 *
 * Parameters:
 *
 * container - DOM node that contains the toolbar.
 */
function mxToolbar(container) {
  this.container = container;
}

/**
 * Extends mxEventSource.
 */
mxToolbar.prototype = new mxEventSource();
mxToolbar.prototype.constructor = mxToolbar;

/**
 * Variable: container
 *
 * Reference to the DOM nodes that contains the toolbar.
 */
mxToolbar.prototype.container = null;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxToolbar.prototype.enabled = true;

/**
 * Variable: noReset
 *
 * Specifies if <resetMode> requires a forced flag of true for resetting
 * the current mode in the toolbar. Default is false. This is set to true
 * if the toolbar item is double clicked to avoid a reset after a single
 * use of the item.
 */
mxToolbar.prototype.noReset = false;

/**
 * Variable: updateDefaultMode
 *
 * Boolean indicating if the default mode should be the last selected
 * switch mode or the first inserted switch mode. Default is true, that
 * is the last selected switch mode is the default mode. The default mode
 * is the mode to be selected after a reset of the toolbar. If this is
 * false, then the default mode is the first inserted mode item regardless
 * of what was last selected. Otherwise, the selected item after a reset is
 * the previously selected item.
 */
mxToolbar.prototype.updateDefaultMode = true;

/**
 * Function: addItem
 *
 * Adds the given function as an image with the specified title and icon
 * and returns the new image node.
 *
 * Parameters:
 *
 * title - Optional string that is used as the tooltip.
 * icon - Optional URL of the image to be used. If no URL is given, then a
 * button is created.
 * funct - Function to execute on a mouse click.
 * pressedIcon - Optional URL of the pressed image. Default is a gray
 * background.
 * style - Optional style classname. Default is mxToolbarItem.
 * factoryMethod - Optional factory method for popup menu, eg.
 * function(menu, evt, cell) { menu.addItem('Hello, World!'); }
 */
mxToolbar.prototype.addItem = function (title, icon, funct, pressedIcon, style, factoryMethod) {
  var img = document.createElement(icon != null ? 'img' : 'button');
  var initialClassName = style || (factoryMethod != null ? 'mxToolbarMode' : 'mxToolbarItem');
  img.className = initialClassName;
  img.setAttribute('src', icon);

  if (title != null) {
    if (icon != null) {
      img.setAttribute('title', title);
    } else {
      mxUtils.write(img, title);
    }
  }

  this.container.appendChild(img);

  // Invokes the function on a click on the toolbar item
  if (funct != null) {
    mxEvent.addListener(img, 'click', funct);

    if (mxClient.IS_TOUCH) {
      mxEvent.addListener(img, 'touchend', funct);
    }
  }

  var mouseHandler = mxUtils.bind(this, function (evt) {
    if (pressedIcon != null) {
      img.setAttribute('src', icon);
    } else {
      img.style.backgroundColor = '';
    }
  });

  // Highlights the toolbar item with a gray background
  // while it is being clicked with the mouse
  mxEvent.addGestureListeners(
    img,
    mxUtils.bind(this, function (evt) {
      if (pressedIcon != null) {
        img.setAttribute('src', pressedIcon);
      } else {
        img.style.backgroundColor = 'gray';
      }

      // Popup Menu
      if (factoryMethod != null) {
        if (this.menu == null) {
          this.menu = new mxPopupMenu();
          this.menu.init();
        }

        var last = this.currentImg;

        if (this.menu.isMenuShowing()) {
          this.menu.hideMenu();
        }

        if (last != img) {
          // Redirects factory method to local factory method
          this.currentImg = img;
          this.menu.factoryMethod = factoryMethod;

          var point = new mxPoint(img.offsetLeft, img.offsetTop + img.offsetHeight);
          this.menu.popup(point.x, point.y, null, evt);

          // Sets and overrides to restore classname
          if (this.menu.isMenuShowing()) {
            img.className = initialClassName + 'Selected';

            this.menu.hideMenu = function () {
              mxPopupMenu.prototype.hideMenu.apply(this);
              img.className = initialClassName;
              this.currentImg = null;
            };
          }
        }
      }
    }),
    null,
    mouseHandler
  );

  mxEvent.addListener(img, 'mouseout', mouseHandler);

  return img;
};

/**
 * Function: addCombo
 *
 * Adds and returns a new SELECT element using the given style. The element
 * is placed inside a DIV with the mxToolbarComboContainer style classname.
 *
 * Parameters:
 *
 * style - Optional style classname. Default is mxToolbarCombo.
 */
mxToolbar.prototype.addCombo = function (style) {
  var div = document.createElement('div');
  div.style.display = 'inline';
  div.className = 'mxToolbarComboContainer';

  var select = document.createElement('select');
  select.className = style || 'mxToolbarCombo';
  div.appendChild(select);

  this.container.appendChild(div);

  return select;
};

/**
 * Function: addActionCombo
 *
 * Adds and returns a new SELECT element using the given title as the
 * default element. The selection is reset to this element after each
 * change.
 *
 * Parameters:
 *
 * title - String that specifies the title of the default element.
 * style - Optional style classname. Default is mxToolbarCombo.
 */
mxToolbar.prototype.addActionCombo = function (title, style) {
  var select = document.createElement('select');
  select.className = style || 'mxToolbarCombo';
  this.addOption(select, title, null);

  mxEvent.addListener(select, 'change', function (evt) {
    var value = select.options[select.selectedIndex];
    select.selectedIndex = 0;

    if (value.funct != null) {
      value.funct(evt);
    }
  });

  this.container.appendChild(select);

  return select;
};

/**
 * Function: addOption
 *
 * Adds and returns a new OPTION element inside the given SELECT element.
 * If the given value is a function then it is stored in the option's funct
 * field.
 *
 * Parameters:
 *
 * combo - SELECT element that will contain the new entry.
 * title - String that specifies the title of the option.
 * value - Specifies the value associated with this option.
 */
mxToolbar.prototype.addOption = function (combo, title, value) {
  var option = document.createElement('option');
  mxUtils.writeln(option, title);

  if (typeof value == 'function') {
    option.funct = value;
  } else {
    option.setAttribute('value', value);
  }

  combo.appendChild(option);

  return option;
};

/**
 * Function: addSwitchMode
 *
 * Adds a new selectable item to the toolbar. Only one switch mode item may
 * be selected at a time. The currently selected item is the default item
 * after a reset of the toolbar.
 */
mxToolbar.prototype.addSwitchMode = function (title, icon, funct, pressedIcon, style) {
  var img = document.createElement('img');
  img.initialClassName = style || 'mxToolbarMode';
  img.className = img.initialClassName;
  img.setAttribute('src', icon);
  img.altIcon = pressedIcon;

  if (title != null) {
    img.setAttribute('title', title);
  }

  mxEvent.addListener(
    img,
    'click',
    mxUtils.bind(this, function (evt) {
      var tmp = this.selectedMode.altIcon;

      if (tmp != null) {
        this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
        this.selectedMode.setAttribute('src', tmp);
      } else {
        this.selectedMode.className = this.selectedMode.initialClassName;
      }

      if (this.updateDefaultMode) {
        this.defaultMode = img;
      }

      this.selectedMode = img;

      var tmp = img.altIcon;

      if (tmp != null) {
        img.altIcon = img.getAttribute('src');
        img.setAttribute('src', tmp);
      } else {
        img.className = img.initialClassName + 'Selected';
      }

      this.fireEvent(new mxEventObject(mxEvent.SELECT));
      funct();
    })
  );

  this.container.appendChild(img);

  if (this.defaultMode == null) {
    this.defaultMode = img;

    // Function should fire only once so
    // do not pass it with the select event
    this.selectMode(img);
    funct();
  }

  return img;
};

/**
 * Function: addMode
 *
 * Adds a new item to the toolbar. The selection is typically reset after
 * the item has been consumed, for example by adding a new vertex to the
 * graph. The reset is not carried out if the item is double clicked.
 *
 * The function argument uses the following signature: funct(evt, cell) where
 * evt is the native mouse event and cell is the cell under the mouse.
 */
mxToolbar.prototype.addMode = function (title, icon, funct, pressedIcon, style, toggle) {
  toggle = toggle != null ? toggle : true;
  var img = document.createElement(icon != null ? 'img' : 'button');

  img.initialClassName = style || 'mxToolbarMode';
  img.className = img.initialClassName;
  img.setAttribute('src', icon);
  img.altIcon = pressedIcon;

  if (title != null) {
    img.setAttribute('title', title);
  }

  if (this.enabled && toggle) {
    mxEvent.addListener(
      img,
      'click',
      mxUtils.bind(this, function (evt) {
        this.selectMode(img, funct);
        this.noReset = false;
      })
    );

    mxEvent.addListener(
      img,
      'dblclick',
      mxUtils.bind(this, function (evt) {
        this.selectMode(img, funct);
        this.noReset = true;
      })
    );

    if (this.defaultMode == null) {
      this.defaultMode = img;
      this.defaultFunction = funct;
      this.selectMode(img, funct);
    }
  }

  this.container.appendChild(img);

  return img;
};

/**
 * Function: selectMode
 *
 * Resets the state of the previously selected mode and displays the given
 * DOM node as selected. This function fires a select event with the given
 * function as a parameter.
 */
mxToolbar.prototype.selectMode = function (domNode, funct) {
  if (this.selectedMode != domNode) {
    if (this.selectedMode != null) {
      var tmp = this.selectedMode.altIcon;

      if (tmp != null) {
        this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
        this.selectedMode.setAttribute('src', tmp);
      } else {
        this.selectedMode.className = this.selectedMode.initialClassName;
      }
    }

    this.selectedMode = domNode;
    var tmp = this.selectedMode.altIcon;

    if (tmp != null) {
      this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
      this.selectedMode.setAttribute('src', tmp);
    } else {
      this.selectedMode.className = this.selectedMode.initialClassName + 'Selected';
    }

    this.fireEvent(new mxEventObject(mxEvent.SELECT, 'function', funct));
  }
};

/**
 * Function: resetMode
 *
 * Selects the default mode and resets the state of the previously selected
 * mode.
 */
mxToolbar.prototype.resetMode = function (forced) {
  if ((forced || !this.noReset) && this.selectedMode != this.defaultMode) {
    // The last selected switch mode will be activated
    // so the function was already executed and is
    // no longer required here
    this.selectMode(this.defaultMode, this.defaultFunction);
  }
};

/**
 * Function: addSeparator
 *
 * Adds the specifies image as a separator.
 *
 * Parameters:
 *
 * icon - URL of the separator icon.
 */
mxToolbar.prototype.addSeparator = function (icon) {
  return this.addItem(null, icon, null);
};

/**
 * Function: addBreak
 *
 * Adds a break to the container.
 */
mxToolbar.prototype.addBreak = function () {
  mxUtils.br(this.container);
};

/**
 * Function: addLine
 *
 * Adds a horizontal line to the container.
 */
mxToolbar.prototype.addLine = function () {
  var hr = document.createElement('hr');

  hr.style.marginRight = '6px';
  hr.setAttribute('size', '1');

  this.container.appendChild(hr);
};

/**
 * Function: destroy
 *
 * Removes the toolbar and all its associated resources.
 */
mxToolbar.prototype.destroy = function () {
  mxEvent.release(this.container);
  this.container = null;
  this.defaultMode = null;
  this.defaultFunction = null;
  this.selectedMode = null;

  if (this.menu != null) {
    this.menu.destroy();
  }
};

/**
 * util/mxUndoableEdit.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxUndoableEdit
 *
 * Implements a composite undoable edit. Here is an example for a custom change
 * which gets executed via the model:
 *
 * (code)
 * function CustomChange(model, name)
 * {
 *   this.model = model;
 *   this.name = name;
 *   this.previous = name;
 * };
 *
 * CustomChange.prototype.execute = function()
 * {
 *   var tmp = this.model.name;
 *   this.model.name = this.previous;
 *   this.previous = tmp;
 * };
 *
 * var name = prompt('Enter name');
 * graph.model.execute(new CustomChange(graph.model, name));
 * (end)
 *
 * Event: mxEvent.EXECUTED
 *
 * Fires between START_EDIT and END_EDIT after an atomic change was executed.
 * The <code>change</code> property contains the change that was executed.
 *
 * Event: mxEvent.START_EDIT
 *
 * Fires before a set of changes will be executed in <undo> or <redo>.
 * This event contains no properties.
 *
 * Event: mxEvent.END_EDIT
 *
 * Fires after a set of changeswas executed in <undo> or <redo>.
 * This event contains no properties.
 *
 * Constructor: mxUndoableEdit
 *
 * Constructs a new undoable edit for the given source.
 */
function mxUndoableEdit(source, significant) {
  this.source = source;
  this.changes = [];
  this.significant = significant != null ? significant : true;
}

/**
 * Variable: source
 *
 * Specifies the source of the edit.
 */
mxUndoableEdit.prototype.source = null;

/**
 * Variable: changes
 *
 * Array that contains the changes that make up this edit. The changes are
 * expected to either have an undo and redo function, or an execute
 * function. Default is an empty array.
 */
mxUndoableEdit.prototype.changes = null;

/**
 * Variable: significant
 *
 * Specifies if the undoable change is significant.
 * Default is true.
 */
mxUndoableEdit.prototype.significant = null;

/**
 * Variable: undone
 *
 * Specifies if this edit has been undone. Default is false.
 */
mxUndoableEdit.prototype.undone = false;

/**
 * Variable: redone
 *
 * Specifies if this edit has been redone. Default is false.
 */
mxUndoableEdit.prototype.redone = false;

/**
 * Function: isEmpty
 *
 * Returns true if the this edit contains no changes.
 */
mxUndoableEdit.prototype.isEmpty = function () {
  return this.changes.length == 0;
};

/**
 * Function: isSignificant
 *
 * Returns <significant>.
 */
mxUndoableEdit.prototype.isSignificant = function () {
  return this.significant;
};

/**
 * Function: add
 *
 * Adds the specified change to this edit. The change is an object that is
 * expected to either have an undo and redo, or an execute function.
 */
mxUndoableEdit.prototype.add = function (change) {
  this.changes.push(change);
};

/**
 * Function: notify
 *
 * Hook to notify any listeners of the changes after an <undo> or <redo>
 * has been carried out. This implementation is empty.
 */
mxUndoableEdit.prototype.notify = function () {};

/**
 * Function: die
 *
 * Hook to free resources after the edit has been removed from the command
 * history. This implementation is empty.
 */
mxUndoableEdit.prototype.die = function () {};

/**
 * Function: undo
 *
 * Undoes all changes in this edit.
 */
mxUndoableEdit.prototype.undo = function () {
  if (!this.undone) {
    this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
    var count = this.changes.length;

    for (var i = count - 1; i >= 0; i--) {
      var change = this.changes[i];

      if (change.execute != null) {
        change.execute();
      } else if (change.undo != null) {
        change.undo();
      }

      // New global executed event
      this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
    }

    this.undone = true;
    this.redone = false;
    this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
  }

  this.notify();
};

/**
 * Function: redo
 *
 * Redoes all changes in this edit.
 */
mxUndoableEdit.prototype.redo = function () {
  if (!this.redone) {
    this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
    var count = this.changes.length;

    for (var i = 0; i < count; i++) {
      var change = this.changes[i];

      if (change.execute != null) {
        change.execute();
      } else if (change.redo != null) {
        change.redo();
      }

      // New global executed event
      this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
    }

    this.undone = false;
    this.redone = true;
    this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
  }

  this.notify();
};

/**
 * util/mxUndoManager.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxUndoManager
 *
 * Implements a command history. When changing the graph model, an
 * <mxUndoableChange> object is created at the start of the transaction (when
 * model.beginUpdate is called). All atomic changes are then added to this
 * object until the last model.endUpdate call, at which point the
 * <mxUndoableEdit> is dispatched in an event, and added to the history inside
 * <mxUndoManager>. This is done by an event listener in
 * <mxEditor.installUndoHandler>.
 *
 * Each atomic change of the model is represented by an object (eg.
 * <mxRootChange>, <mxChildChange>, <mxTerminalChange> etc) which contains the
 * complete undo information. The <mxUndoManager> also listens to the
 * <mxGraphView> and stores it's changes to the current root as insignificant
 * undoable changes, so that drilling (step into, step up) is undone.
 *
 * This means when you execute an atomic change on the model, then change the
 * current root on the view and click undo, the change of the root will be
 * undone together with the change of the model so that the display represents
 * the state at which the model was changed. However, these changes are not
 * transmitted for sharing as they do not represent a state change.
 *
 * Example:
 *
 * When adding an undo manager to a graph, make sure to add it
 * to the model and the view as well to maintain a consistent
 * display across multiple undo/redo steps.
 *
 * (code)
 * var undoManager = new mxUndoManager();
 * var listener = function(sender, evt)
 * {
 *   undoManager.undoableEditHappened(evt.getProperty('edit'));
 * };
 * graph.getModel().addListener(mxEvent.UNDO, listener);
 * graph.getView().addListener(mxEvent.UNDO, listener);
 * (end)
 *
 * The code creates a function that informs the undoManager
 * of an undoable edit and binds it to the undo event of
 * <mxGraphModel> and <mxGraphView> using
 * <mxEventSource.addListener>.
 *
 * Event: mxEvent.CLEAR
 *
 * Fires after <clear> was invoked. This event has no properties.
 *
 * Event: mxEvent.UNDO
 *
 * Fires afer a significant edit was undone in <undo>. The <code>edit</code>
 * property contains the <mxUndoableEdit> that was undone.
 *
 * Event: mxEvent.REDO
 *
 * Fires afer a significant edit was redone in <redo>. The <code>edit</code>
 * property contains the <mxUndoableEdit> that was redone.
 *
 * Event: mxEvent.ADD
 *
 * Fires after an undoable edit was added to the history. The <code>edit</code>
 * property contains the <mxUndoableEdit> that was added.
 *
 * Constructor: mxUndoManager
 *
 * Constructs a new undo manager with the given history size. If no history
 * size is given, then a default size of 100 steps is used.
 */
function mxUndoManager(size) {
  this.size = size != null ? size : 100;
  this.clear();
}

/**
 * Extends mxEventSource.
 */
mxUndoManager.prototype = new mxEventSource();
mxUndoManager.prototype.constructor = mxUndoManager;

/**
 * Variable: size
 *
 * Maximum command history size. 0 means unlimited history. Default is
 * 100.
 */
mxUndoManager.prototype.size = null;

/**
 * Variable: history
 *
 * Array that contains the steps of the command history.
 */
mxUndoManager.prototype.history = null;

/**
 * Variable: indexOfNextAdd
 *
 * Index of the element to be added next.
 */
mxUndoManager.prototype.indexOfNextAdd = 0;

/**
 * Function: isEmpty
 *
 * Returns true if the history is empty.
 */
mxUndoManager.prototype.isEmpty = function () {
  return this.history.length == 0;
};

/**
 * Function: clear
 *
 * Clears the command history.
 */
mxUndoManager.prototype.clear = function () {
  this.history = [];
  this.indexOfNextAdd = 0;
  this.fireEvent(new mxEventObject(mxEvent.CLEAR));
};

/**
 * Function: canUndo
 *
 * Returns true if an undo is possible.
 */
mxUndoManager.prototype.canUndo = function () {
  return this.indexOfNextAdd > 0;
};

/**
 * Function: undo
 *
 * Undoes the last change.
 */
mxUndoManager.prototype.undo = function () {
  while (this.indexOfNextAdd > 0) {
    var edit = this.history[--this.indexOfNextAdd];
    edit.undo();

    if (edit.isSignificant()) {
      this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
      break;
    }
  }
};

/**
 * Function: canRedo
 *
 * Returns true if a redo is possible.
 */
mxUndoManager.prototype.canRedo = function () {
  return this.indexOfNextAdd < this.history.length;
};

/**
 * Function: redo
 *
 * Redoes the last change.
 */
mxUndoManager.prototype.redo = function () {
  var n = this.history.length;

  while (this.indexOfNextAdd < n) {
    var edit = this.history[this.indexOfNextAdd++];
    edit.redo();

    if (edit.isSignificant()) {
      this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));
      break;
    }
  }
};

/**
 * Function: undoableEditHappened
 *
 * Method to be called to add new undoable edits to the <history>.
 */
mxUndoManager.prototype.undoableEditHappened = function (undoableEdit) {
  this.trim();

  if (this.size > 0 && this.size == this.history.length) {
    this.history.shift();
  }

  this.history.push(undoableEdit);
  this.indexOfNextAdd = this.history.length;
  this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));
};

/**
 * Function: trim
 *
 * Removes all pending steps after <indexOfNextAdd> from the history,
 * invoking die on each edit. This is called from <undoableEditHappened>.
 */
mxUndoManager.prototype.trim = function () {
  if (this.history.length > this.indexOfNextAdd) {
    var edits = this.history.splice(this.indexOfNextAdd, this.history.length - this.indexOfNextAdd);

    for (var i = 0; i < edits.length; i++) {
      edits[i].die();
    }
  }
};

/**
 * util/mxUrlConverter.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 *
 * Class: mxUrlConverter
 *
 * Converts relative and absolute URLs to absolute URLs with protocol and domain.
 */
var mxUrlConverter = function () {
  // Empty constructor
};

/**
 * Variable: enabled
 *
 * Specifies if the converter is enabled. Default is true.
 */
mxUrlConverter.prototype.enabled = true;

/**
 * Variable: baseUrl
 *
 * Specifies the base URL to be used as a prefix for relative URLs.
 */
mxUrlConverter.prototype.baseUrl = null;

/**
 * Variable: baseDomain
 *
 * Specifies the base domain to be used as a prefix for absolute URLs.
 */
mxUrlConverter.prototype.baseDomain = null;

/**
 * Function: updateBaseUrl
 *
 * Private helper function to update the base URL.
 */
mxUrlConverter.prototype.updateBaseUrl = function () {
  this.baseDomain = location.protocol + '//' + location.host;
  this.baseUrl = this.baseDomain + location.pathname;
  var tmp = this.baseUrl.lastIndexOf('/');

  // Strips filename etc
  if (tmp > 0) {
    this.baseUrl = this.baseUrl.substring(0, tmp + 1);
  }
};

/**
 * Function: isEnabled
 *
 * Returns <enabled>.
 */
mxUrlConverter.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Sets <enabled>.
 */
mxUrlConverter.prototype.setEnabled = function (value) {
  this.enabled = value;
};

/**
 * Function: getBaseUrl
 *
 * Returns <baseUrl>.
 */
mxUrlConverter.prototype.getBaseUrl = function () {
  return this.baseUrl;
};

/**
 * Function: setBaseUrl
 *
 * Sets <baseUrl>.
 */
mxUrlConverter.prototype.setBaseUrl = function (value) {
  this.baseUrl = value;
};

/**
 * Function: getBaseDomain
 *
 * Returns <baseDomain>.
 */
mxUrlConverter.prototype.getBaseDomain = function () {
  return this.baseDomain;
};

/**
 * Function: setBaseDomain
 *
 * Sets <baseDomain>.
 */
mxUrlConverter.prototype.setBaseDomain = function (value) {
  this.baseDomain = value;
};

/**
 * Function: isRelativeUrl
 *
 * Returns true if the given URL is relative.
 */
mxUrlConverter.prototype.isRelativeUrl = function (url) {
  return (
    url != null &&
    url.substring(0, 2) != '//' &&
    url.substring(0, 7) != 'http://' &&
    url.substring(0, 8) != 'https://' &&
    url.substring(0, 10) != 'data:image' &&
    url.substring(0, 7) != 'file://'
  );
};

/**
 * Function: convert
 *
 * Converts the given URL to an absolute URL with protol and domain.
 * Relative URLs are first converted to absolute URLs.
 */
mxUrlConverter.prototype.convert = function (url) {
  if (this.isEnabled() && this.isRelativeUrl(url)) {
    if (this.getBaseUrl() == null) {
      this.updateBaseUrl();
    }

    if (url.charAt(0) == '/') {
      url = this.getBaseDomain() + url;
    } else {
      url = this.getBaseUrl() + url;
    }
  }

  return url;
};

/**
 * util/mxPanningManager.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPanningManager
 *
 * Implements a handler for panning.
 */
function mxPanningManager(graph) {
  this.thread = null;
  this.active = false;
  this.tdx = 0;
  this.tdy = 0;
  this.t0x = 0;
  this.t0y = 0;
  this.dx = 0;
  this.dy = 0;
  this.scrollbars = false;
  this.scrollLeft = 0;
  this.scrollTop = 0;

  this.mouseListener = {
    mouseDown: function (sender, me) {},
    mouseMove: function (sender, me) {},
    mouseUp: mxUtils.bind(this, function (sender, me) {
      if (this.active) {
        this.stop();
      }
    }),
  };

  graph.addMouseListener(this.mouseListener);

  this.mouseUpListener = mxUtils.bind(this, function () {
    if (this.active) {
      this.stop();
    }
  });

  // Stops scrolling on every mouseup anywhere in the document
  mxEvent.addListener(document, 'mouseup', this.mouseUpListener);

  var createThread = mxUtils.bind(this, function () {
    this.scrollbars = mxUtils.hasScrollbars(graph.container);
    this.scrollLeft = graph.container.scrollLeft;
    this.scrollTop = graph.container.scrollTop;

    return window.setInterval(
      mxUtils.bind(this, function () {
        this.tdx -= this.dx;
        this.tdy -= this.dy;

        if (this.scrollbars) {
          var left = -graph.container.scrollLeft - Math.ceil(this.dx);
          var top = -graph.container.scrollTop - Math.ceil(this.dy);
          graph.panGraph(left, top);
          graph.panDx = this.scrollLeft - graph.container.scrollLeft;
          graph.panDy = this.scrollTop - graph.container.scrollTop;
          graph.fireEvent(new mxEventObject(mxEvent.PAN));
          // TODO: Implement graph.autoExtend
        } else {
          graph.panGraph(this.getDx(), this.getDy());
        }
      }),
      this.delay
    );
  });

  this.isActive = function () {
    return active;
  };

  this.getDx = function () {
    return Math.round(this.tdx);
  };

  this.getDy = function () {
    return Math.round(this.tdy);
  };

  this.start = function () {
    this.t0x = graph.view.translate.x;
    this.t0y = graph.view.translate.y;
    this.active = true;
  };

  this.panTo = function (x, y, w, h) {
    if (!this.active) {
      this.start();
    }

    this.scrollLeft = graph.container.scrollLeft;
    this.scrollTop = graph.container.scrollTop;

    w = w != null ? w : 0;
    h = h != null ? h : 0;

    var c = graph.container;
    this.dx = x + w - c.scrollLeft - c.clientWidth;

    if (this.dx < 0 && Math.abs(this.dx) < this.border) {
      this.dx = this.border + this.dx;
    } else if (this.handleMouseOut) {
      this.dx = Math.max(this.dx, 0);
    } else {
      this.dx = 0;
    }

    if (this.dx == 0) {
      this.dx = x - c.scrollLeft;

      if (this.dx > 0 && this.dx < this.border) {
        this.dx = this.dx - this.border;
      } else if (this.handleMouseOut) {
        this.dx = Math.min(0, this.dx);
      } else {
        this.dx = 0;
      }
    }

    this.dy = y + h - c.scrollTop - c.clientHeight;

    if (this.dy < 0 && Math.abs(this.dy) < this.border) {
      this.dy = this.border + this.dy;
    } else if (this.handleMouseOut) {
      this.dy = Math.max(this.dy, 0);
    } else {
      this.dy = 0;
    }

    if (this.dy == 0) {
      this.dy = y - c.scrollTop;

      if (this.dy > 0 && this.dy < this.border) {
        this.dy = this.dy - this.border;
      } else if (this.handleMouseOut) {
        this.dy = Math.min(0, this.dy);
      } else {
        this.dy = 0;
      }
    }

    if (this.dx != 0 || this.dy != 0) {
      this.dx *= this.damper;
      this.dy *= this.damper;

      if (this.thread == null) {
        this.thread = createThread();
      }
    } else if (this.thread != null) {
      window.clearInterval(this.thread);
      this.thread = null;
    }
  };

  this.stop = function () {
    if (this.active) {
      this.active = false;

      if (this.thread != null) {
        window.clearInterval(this.thread);
        this.thread = null;
      }

      this.tdx = 0;
      this.tdy = 0;

      if (!this.scrollbars) {
        var px = graph.panDx;
        var py = graph.panDy;

        if (px != 0 || py != 0) {
          graph.panGraph(0, 0);
          graph.view.setTranslate(this.t0x + px / graph.view.scale, this.t0y + py / graph.view.scale);
        }
      } else {
        graph.panDx = 0;
        graph.panDy = 0;
        graph.fireEvent(new mxEventObject(mxEvent.PAN));
      }
    }
  };

  this.destroy = function () {
    graph.removeMouseListener(this.mouseListener);
    mxEvent.removeListener(document, 'mouseup', this.mouseUpListener);
  };
}

/**
 * Variable: damper
 *
 * Damper value for the panning. Default is 1/6.
 */
mxPanningManager.prototype.damper = 1 / 6;

/**
 * Variable: delay
 *
 * Delay in milliseconds for the panning. Default is 10.
 */
mxPanningManager.prototype.delay = 10;

/**
 * Variable: handleMouseOut
 *
 * Specifies if mouse events outside of the component should be handled. Default is true.
 */
mxPanningManager.prototype.handleMouseOut = true;

/**
 * Variable: border
 *
 * Border to handle automatic panning inside the component. Default is 0 (disabled).
 */
mxPanningManager.prototype.border = 0;

/**
 * util/mxPopupMenu.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPopupMenu
 *
 * Basic popup menu. To add a vertical scrollbar to a given submenu, the
 * following code can be used.
 *
 * (code)
 * var mxPopupMenuShowMenu = mxPopupMenu.prototype.showMenu;
 * mxPopupMenu.prototype.showMenu = function()
 * {
 *   mxPopupMenuShowMenu.apply(this, arguments);
 *
 *   this.div.style.overflowY = 'auto';
 *   this.div.style.overflowX = 'hidden';
 *   this.div.style.maxHeight = '160px';
 * };
 * (end)
 *
 * Constructor: mxPopupMenu
 *
 * Constructs a popupmenu.
 *
 * Event: mxEvent.SHOW
 *
 * Fires after the menu has been shown in <popup>.
 */
function mxPopupMenu(factoryMethod, root) {
  this.factoryMethod = factoryMethod;

  if (factoryMethod != null) {
    this.init(root);
  }
}

/**
 * Extends mxEventSource.
 */
mxPopupMenu.prototype = new mxEventSource();
mxPopupMenu.prototype.constructor = mxPopupMenu;

/**
 * Variable: submenuImage
 *
 * URL of the image to be used for the submenu icon.
 */
mxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + '/submenu.gif';

/**
 * Variable: zIndex
 *
 * Specifies the zIndex for the popupmenu and its shadow. Default is 10006.
 */
mxPopupMenu.prototype.zIndex = 10006;

/**
 * Variable: factoryMethod
 *
 * Function that is used to create the popup menu. The function takes the
 * current panning handler, the <mxCell> under the mouse and the mouse
 * event that triggered the call as arguments.
 */
mxPopupMenu.prototype.factoryMethod = null;

/**
 * Variable: useLeftButtonForPopup
 *
 * Specifies if popupmenus should be activated by clicking the left mouse
 * button. Default is false.
 */
mxPopupMenu.prototype.useLeftButtonForPopup = false;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxPopupMenu.prototype.enabled = true;

/**
 * Variable: itemCount
 *
 * Contains the number of times <addItem> has been called for a new menu.
 */
mxPopupMenu.prototype.itemCount = 0;

/**
 * Variable: autoExpand
 *
 * Specifies if submenus should be expanded on mouseover. Default is false.
 */
mxPopupMenu.prototype.autoExpand = false;

/**
 * Variable: smartSeparators
 *
 * Specifies if separators should only be added if a menu item follows them.
 * Default is false.
 */
mxPopupMenu.prototype.smartSeparators = false;

/**
 * Variable: labels
 *
 * Specifies if any labels should be visible. Default is true.
 */
mxPopupMenu.prototype.labels = true;

/**
 * Function: init
 *
 * Initializes the shapes required for this vertex handler.
 */
mxPopupMenu.prototype.init = function (root) {
  this.root = root || document.body;
  // Adds the inner table
  this.table = document.createElement('table');
  this.table.className = 'mxPopupMenu';

  this.tbody = document.createElement('tbody');
  this.table.appendChild(this.tbody);

  // Adds the outer div
  this.div = document.createElement('div');
  this.div.className = 'mxPopupMenu';
  this.div.style.display = 'inline';
  this.div.style.zIndex = this.zIndex;
  this.div.appendChild(this.table);

  // Disables the context menu on the outer div
  mxEvent.disableContextMenu(this.div);
};

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxPopupMenu.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 */
mxPopupMenu.prototype.setEnabled = function (enabled) {
  this.enabled = enabled;
};

/**
 * Function: isPopupTrigger
 *
 * Returns true if the given event is a popupmenu trigger for the optional
 * given cell.
 *
 * Parameters:
 *
 * me - <mxMouseEvent> that represents the mouse event.
 */
mxPopupMenu.prototype.isPopupTrigger = function (me) {
  return me.isPopupTrigger() || (this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(me.getEvent()));
};

/**
 * Function: addItem
 *
 * Adds the given item to the given parent item. If no parent item is specified
 * then the item is added to the top-level menu. The return value may be used
 * as the parent argument, ie. as a submenu item. The return value is the table
 * row that represents the item.
 *
 * Paramters:
 *
 * title - String that represents the title of the menu item.
 * image - Optional URL for the image icon.
 * funct - Function associated that takes a mouseup or touchend event.
 * parent - Optional item returned by <addItem>.
 * iconCls - Optional string that represents the CSS class for the image icon.
 * IconsCls is ignored if image is given.
 * enabled - Optional boolean indicating if the item is enabled. Default is true.
 * active - Optional boolean indicating if the menu should implement any event handling.
 * Default is true.
 * noHover - Optional boolean to disable hover state.
 */
mxPopupMenu.prototype.addItem = function (title, image, funct, parent, iconCls, enabled, active, noHover) {
  parent = parent || this;
  this.itemCount++;

  // Smart separators only added if element contains items
  if (parent.willAddSeparator) {
    if (parent.containsItems) {
      this.addSeparator(parent, true);
    }

    parent.willAddSeparator = false;
  }

  parent.containsItems = true;
  var tr = document.createElement('tr');
  tr.className = 'mxPopupMenuItem';
  var col1 = document.createElement('td');
  col1.className = 'mxPopupMenuIcon';

  // Adds the given image into the first column
  if (image != null) {
    var img = document.createElement('img');
    img.src = image;
    col1.appendChild(img);
  } else if (iconCls != null) {
    var div = document.createElement('div');
    div.className = iconCls;
    col1.appendChild(div);
  }

  tr.appendChild(col1);

  if (this.labels) {
    var col2 = document.createElement('td');
    col2.className = 'mxPopupMenuItem' + (enabled != null && !enabled ? ' mxDisabled' : '');

    mxUtils.write(col2, title);
    col2.align = 'left';
    tr.appendChild(col2);

    var col3 = document.createElement('td');
    col3.className = 'mxPopupMenuItem' + (enabled != null && !enabled ? ' mxDisabled' : '');
    col3.style.paddingRight = '6px';
    col3.style.textAlign = 'right';

    tr.appendChild(col3);

    if (parent.div == null) {
      this.createSubmenu(parent);
    }
  }

  parent.tbody.appendChild(tr);

  if (active != false && enabled != false) {
    var currentSelection = null;

    mxEvent.addGestureListeners(
      tr,
      mxUtils.bind(this, function (evt) {
        this.eventReceiver = tr;

        if (parent.activeRow != tr && parent.activeRow != parent) {
          if (parent.activeRow != null && parent.activeRow.div.parentNode != null) {
            this.hideSubmenu(parent);
          }

          if (tr.div != null) {
            this.showSubmenu(parent, tr);
            parent.activeRow = tr;
          }
        }

        // Workaround for lost current selection in page because of focus in IE
        if (document.selection != null && (mxClient.IS_QUIRKS || document.documentMode == 8)) {
          currentSelection = document.selection.createRange();
        }

        mxEvent.consume(evt);
      }),
      mxUtils.bind(this, function (evt) {
        if (parent.activeRow != tr && parent.activeRow != parent) {
          if (parent.activeRow != null && parent.activeRow.div.parentNode != null) {
            this.hideSubmenu(parent);
          }

          if (this.autoExpand && tr.div != null) {
            this.showSubmenu(parent, tr);
            parent.activeRow = tr;
          }
        }

        // Sets hover style because TR in IE doesn't have hover
        if (!noHover) {
          tr.className = 'mxPopupMenuItemHover';
        }
      }),
      mxUtils.bind(this, function (evt) {
        // EventReceiver avoids clicks on a submenu item
        // which has just been shown in the mousedown
        if (this.eventReceiver == tr) {
          if (parent.activeRow != tr) {
            this.hideMenu();
          }

          // Workaround for lost current selection in page because of focus in IE
          if (currentSelection != null) {
            // Workaround for "unspecified error" in IE8 standards
            try {
              currentSelection.select();
            } catch (e) {
              // ignore
            }

            currentSelection = null;
          }

          if (funct != null) {
            funct(evt);
          }
        }

        this.eventReceiver = null;
        mxEvent.consume(evt);
      })
    );

    // Resets hover style because TR in IE doesn't have hover
    if (!noHover) {
      mxEvent.addListener(
        tr,
        'mouseout',
        mxUtils.bind(this, function (evt) {
          tr.className = 'mxPopupMenuItem';
        })
      );
    }
  }

  return tr;
};

/**
 * Adds a checkmark to the given menuitem.
 */
mxPopupMenu.prototype.addCheckmark = function (item, img) {
  var td = item.firstChild.nextSibling;
  td.style.backgroundImage = "url('" + img + "')";
  td.style.backgroundRepeat = 'no-repeat';
  td.style.backgroundPosition = '2px 50%';
};

/**
 * Function: createSubmenu
 *
 * Creates the nodes required to add submenu items inside the given parent
 * item. This is called in <addItem> if a parent item is used for the first
 * time. This adds various DOM nodes and a <submenuImage> to the parent.
 *
 * Parameters:
 *
 * parent - An item returned by <addItem>.
 */
mxPopupMenu.prototype.createSubmenu = function (parent) {
  parent.table = document.createElement('table');
  parent.table.className = 'mxPopupMenu';

  parent.tbody = document.createElement('tbody');
  parent.table.appendChild(parent.tbody);

  parent.div = document.createElement('div');
  parent.div.className = 'mxPopupMenu';

  parent.div.style.position = 'absolute';
  parent.div.style.display = 'inline';
  parent.div.style.zIndex = this.zIndex;

  parent.div.appendChild(parent.table);

  var img = document.createElement('img');
  img.setAttribute('src', this.submenuImage);

  // Last column of the submenu item in the parent menu
  td = parent.firstChild.nextSibling.nextSibling;
  td.appendChild(img);
};

/**
 * Function: showSubmenu
 *
 * Shows the submenu inside the given parent row.
 */
mxPopupMenu.prototype.showSubmenu = function (parent, row) {
  if (row.div != null) {
    row.div.style.left = parent.div.offsetLeft + row.offsetLeft + row.offsetWidth - 1 + 'px';
    row.div.style.top = parent.div.offsetTop + row.offsetTop + 'px';
    document.body.appendChild(row.div);

    // Moves the submenu to the left side if there is no space
    var left = parseInt(row.div.offsetLeft);
    var width = parseInt(row.div.offsetWidth);
    var offset = mxUtils.getDocumentScrollOrigin(document);

    var b = document.body;
    var d = document.documentElement;

    var right = offset.x + (b.clientWidth || d.clientWidth);

    if (left + width > right) {
      row.div.style.left = Math.max(0, parent.div.offsetLeft - width + (mxClient.IS_IE ? 6 : -6)) + 'px';
    }

    mxUtils.fit(row.div);
  }
};

/**
 * Function: addSeparator
 *
 * Adds a horizontal separator in the given parent item or the top-level menu
 * if no parent is specified.
 *
 * Parameters:
 *
 * parent - Optional item returned by <addItem>.
 * force - Optional boolean to ignore <smartSeparators>. Default is false.
 */
mxPopupMenu.prototype.addSeparator = function (parent, force) {
  parent = parent || this;

  if (this.smartSeparators && !force) {
    parent.willAddSeparator = true;
  } else if (parent.tbody != null) {
    parent.willAddSeparator = false;
    var tr = document.createElement('tr');

    var col1 = document.createElement('td');
    col1.className = 'mxPopupMenuIcon';
    col1.style.padding = '0 0 0 0px';

    tr.appendChild(col1);

    var col2 = document.createElement('td');
    col2.style.padding = '0 0 0 0px';
    col2.setAttribute('colSpan', '2');

    var hr = document.createElement('hr');
    hr.setAttribute('size', '1');
    col2.appendChild(hr);

    tr.appendChild(col2);

    parent.tbody.appendChild(tr);
  }
};

/**
 * Function: popup
 *
 * Shows the popup menu for the given event and cell.
 *
 * Example:
 *
 * (code)
 * graph.panningHandler.popup = function(x, y, cell, evt)
 * {
 *   mxUtils.alert('Hello, World!');
 * }
 * (end)
 */
mxPopupMenu.prototype.popup = function (x, y, cell, evt) {
  if (this.div != null && this.tbody != null && this.factoryMethod != null) {
    this.div.style.left = x + 'px';
    this.div.style.top = y + 'px';

    // Removes all child nodes from the existing menu
    while (this.tbody.firstChild != null) {
      mxEvent.release(this.tbody.firstChild);
      this.tbody.removeChild(this.tbody.firstChild);
    }

    this.itemCount = 0;
    this.factoryMethod(this, cell, evt);

    if (this.itemCount > 0) {
      this.showMenu();
      this.fireEvent(new mxEventObject(mxEvent.SHOW));
    }
  }
};

/**
 * Function: isMenuShowing
 *
 * Returns true if the menu is showing.
 */
mxPopupMenu.prototype.isMenuShowing = function () {
  return this.div != null && this.div.parentNode == document.body;
};

/**
 * Function: showMenu
 *
 * Shows the menu.
 */
mxPopupMenu.prototype.showMenu = function () {
  // Disables filter-based shadow in IE9 standards mode
  if (document.documentMode >= 9) {
    this.div.style.filter = 'none';
  }

  // Fits the div inside the viewport
  (this.root || document.body).appendChild(this.div);
  // mxUtils.fit(this.div);
};

/**
 * Function: hideMenu
 *
 * Removes the menu and all submenus.
 */
mxPopupMenu.prototype.hideMenu = function () {
  if (this.div != null) {
    if (this.div.parentNode != null) {
      this.div.parentNode.removeChild(this.div);
    }

    this.hideSubmenu(this);
    this.containsItems = false;
    this.fireEvent(new mxEventObject(mxEvent.HIDE));
  }
};

/**
 * Function: hideSubmenu
 *
 * Removes all submenus inside the given parent.
 *
 * Parameters:
 *
 * parent - An item returned by <addItem>.
 */
mxPopupMenu.prototype.hideSubmenu = function (parent) {
  if (parent.activeRow != null) {
    this.hideSubmenu(parent.activeRow);

    if (parent.activeRow.div.parentNode != null) {
      parent.activeRow.div.parentNode.removeChild(parent.activeRow.div);
    }

    parent.activeRow = null;
  }
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes.
 */
mxPopupMenu.prototype.destroy = function () {
  if (this.div != null) {
    mxEvent.release(this.div);

    if (this.div.parentNode != null) {
      this.div.parentNode.removeChild(this.div);
    }

    this.div = null;
  }
};

/**
 * util/mxAutoSaveManager.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxAutoSaveManager
 *
 * Manager for automatically saving diagrams. The <save> hook must be
 * implemented.
 *
 * Example:
 *
 * (code)
 * var mgr = new mxAutoSaveManager(editor.graph);
 * mgr.save = function()
 * {
 *   mxLog.show();
 *   mxLog.debug('save');
 * };
 * (end)
 *
 * Constructor: mxAutoSaveManager
 *
 * Constructs a new automatic layout for the given graph.
 *
 * Arguments:
 *
 * graph - Reference to the enclosing graph.
 */
function mxAutoSaveManager(graph) {
  // Notifies the manager of a change
  this.changeHandler = mxUtils.bind(this, function (sender, evt) {
    if (this.isEnabled()) {
      this.graphModelChanged(evt.getProperty('edit').changes);
    }
  });

  this.setGraph(graph);
}

/**
 * Extends mxEventSource.
 */
mxAutoSaveManager.prototype = new mxEventSource();
mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxAutoSaveManager.prototype.graph = null;

/**
 * Variable: autoSaveDelay
 *
 * Minimum amount of seconds between two consecutive autosaves. Eg. a
 * value of 1 (s) means the graph is not stored more than once per second.
 * Default is 10.
 */
mxAutoSaveManager.prototype.autoSaveDelay = 10;

/**
 * Variable: autoSaveThrottle
 *
 * Minimum amount of seconds between two consecutive autosaves triggered by
 * more than <autoSaveThreshhold> changes within a timespan of less than
 * <autoSaveDelay> seconds. Eg. a value of 1 (s) means the graph is not
 * stored more than once per second even if there are more than
 * <autoSaveThreshold> changes within that timespan. Default is 2.
 */
mxAutoSaveManager.prototype.autoSaveThrottle = 2;

/**
 * Variable: autoSaveThreshold
 *
 * Minimum amount of ignored changes before an autosave. Eg. a value of 2
 * means after 2 change of the graph model the autosave will trigger if the
 * condition below is true. Default is 5.
 */
mxAutoSaveManager.prototype.autoSaveThreshold = 5;

/**
 * Variable: ignoredChanges
 *
 * Counter for ignored changes in autosave.
 */
mxAutoSaveManager.prototype.ignoredChanges = 0;

/**
 * Variable: lastSnapshot
 *
 * Used for autosaving. See <autosave>.
 */
mxAutoSaveManager.prototype.lastSnapshot = 0;

/**
 * Variable: enabled
 *
 * Specifies if event handling is enabled. Default is true.
 */
mxAutoSaveManager.prototype.enabled = true;

/**
 * Variable: changeHandler
 *
 * Holds the function that handles graph model changes.
 */
mxAutoSaveManager.prototype.changeHandler = null;

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxAutoSaveManager.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 *
 * Parameters:
 *
 * enabled - Boolean that specifies the new enabled state.
 */
mxAutoSaveManager.prototype.setEnabled = function (value) {
  this.enabled = value;
};

/**
 * Function: setGraph
 *
 * Sets the graph that the layouts operate on.
 */
mxAutoSaveManager.prototype.setGraph = function (graph) {
  if (this.graph != null) {
    this.graph.getModel().removeListener(this.changeHandler);
  }

  this.graph = graph;

  if (this.graph != null) {
    this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
  }
};

/**
 * Function: save
 *
 * Empty hook that is called if the graph should be saved.
 */
mxAutoSaveManager.prototype.save = function () {
  // empty
};

/**
 * Function: graphModelChanged
 *
 * Invoked when the graph model has changed.
 */
mxAutoSaveManager.prototype.graphModelChanged = function (changes) {
  var now = new Date().getTime();
  var dt = (now - this.lastSnapshot) / 1000;

  if (dt > this.autoSaveDelay || (this.ignoredChanges >= this.autoSaveThreshold && dt > this.autoSaveThrottle)) {
    this.save();
    this.reset();
  } else {
    // Increments the number of ignored changes
    this.ignoredChanges++;
  }
};

/**
 * Function: reset
 *
 * Resets all counters.
 */
mxAutoSaveManager.prototype.reset = function () {
  this.lastSnapshot = new Date().getTime();
  this.ignoredChanges = 0;
};

/**
 * Function: destroy
 *
 * Removes all handlers from the <graph> and deletes the reference to it.
 */
mxAutoSaveManager.prototype.destroy = function () {
  this.setGraph(null);
};

/**
 * util/mxAnimation.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 *
 * Class: mxAnimation
 *
 * Implements a basic animation in JavaScript.
 *
 * Constructor: mxAnimation
 *
 * Constructs an animation.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 */
function mxAnimation(delay) {
  this.delay = delay != null ? delay : 20;
}

/**
 * Extends mxEventSource.
 */
mxAnimation.prototype = new mxEventSource();
mxAnimation.prototype.constructor = mxAnimation;

/**
 * Variable: delay
 *
 * Specifies the delay between the animation steps. Defaul is 30ms.
 */
mxAnimation.prototype.delay = null;

/**
 * Variable: thread
 *
 * Reference to the thread while the animation is running.
 */
mxAnimation.prototype.thread = null;

/**
 * Function: isRunning
 *
 * Returns true if the animation is running.
 */
mxAnimation.prototype.isRunning = function () {
  return this.thread != null;
};

/**
 * Function: startAnimation
 *
 * Starts the animation by repeatedly invoking updateAnimation.
 */
mxAnimation.prototype.startAnimation = function () {
  if (this.thread == null) {
    this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay);
  }
};

/**
 * Function: updateAnimation
 *
 * Hook for subclassers to implement the animation. Invoke stopAnimation
 * when finished, startAnimation to resume. This is called whenever the
 * timer fires and fires an mxEvent.EXECUTE event with no properties.
 */
mxAnimation.prototype.updateAnimation = function () {
  this.fireEvent(new mxEventObject(mxEvent.EXECUTE));
};

/**
 * Function: stopAnimation
 *
 * Stops the animation by deleting the timer and fires an <mxEvent.DONE>.
 */
mxAnimation.prototype.stopAnimation = function () {
  if (this.thread != null) {
    window.clearInterval(this.thread);
    this.thread = null;
    this.fireEvent(new mxEventObject(mxEvent.DONE));
  }
};

/**
 * util/mxMorphing.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 *
 * Class: mxMorphing
 *
 * Implements animation for morphing cells. Here is an example of
 * using this class for animating the result of a layout algorithm:
 *
 * (code)
 * graph.getModel().beginUpdate();
 * try
 * {
 *   var circleLayout = new mxCircleLayout(graph);
 *   circleLayout.execute(graph.getDefaultParent());
 * }
 * finally
 * {
 *   var morph = new mxMorphing(graph);
 *   morph.addListener(mxEvent.DONE, function()
 *   {
 *     graph.getModel().endUpdate();
 *   });
 *
 *   morph.startAnimation();
 * }
 * (end)
 *
 * Constructor: mxMorphing
 *
 * Constructs an animation.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 * steps - Optional number of steps in the morphing animation. Default is 6.
 * ease - Optional easing constant for the animation. Default is 1.5.
 * delay - Optional delay between the animation steps. Passed to <mxAnimation>.
 */
function mxMorphing(graph, steps, ease, delay) {
  mxAnimation.call(this, delay);
  this.graph = graph;
  this.steps = steps != null ? steps : 6;
  this.ease = ease != null ? ease : 1.5;
}

/**
 * Extends mxEventSource.
 */
mxMorphing.prototype = new mxAnimation();
mxMorphing.prototype.constructor = mxMorphing;

/**
 * Variable: graph
 *
 * Specifies the delay between the animation steps. Defaul is 30ms.
 */
mxMorphing.prototype.graph = null;

/**
 * Variable: steps
 *
 * Specifies the maximum number of steps for the morphing.
 */
mxMorphing.prototype.steps = null;

/**
 * Variable: step
 *
 * Contains the current step.
 */
mxMorphing.prototype.step = 0;

/**
 * Variable: ease
 *
 * Ease-off for movement towards the given vector. Larger values are
 * slower and smoother. Default is 4.
 */
mxMorphing.prototype.ease = null;

/**
 * Variable: cells
 *
 * Optional array of cells to be animated. If this is not specified
 * then all cells are checked and animated if they have been moved
 * in the current transaction.
 */
mxMorphing.prototype.cells = null;

/**
 * Function: updateAnimation
 *
 * Animation step.
 */
mxMorphing.prototype.updateAnimation = function () {
  mxAnimation.prototype.updateAnimation.apply(this, arguments);
  var move = new mxCellStatePreview(this.graph);

  if (this.cells != null) {
    // Animates the given cells individually without recursion
    for (var i = 0; i < this.cells.length; i++) {
      this.animateCell(this.cells[i], move, false);
    }
  } else {
    // Animates all changed cells by using recursion to find
    // the changed cells but not for the animation itself
    this.animateCell(this.graph.getModel().getRoot(), move, true);
  }

  this.show(move);

  if (move.isEmpty() || this.step++ >= this.steps) {
    this.stopAnimation();
  }
};

/**
 * Function: show
 *
 * Shows the changes in the given <mxCellStatePreview>.
 */
mxMorphing.prototype.show = function (move) {
  move.show();
};

/**
 * Function: animateCell
 *
 * Animates the given cell state using <mxCellStatePreview.moveState>.
 */
mxMorphing.prototype.animateCell = function (cell, move, recurse) {
  var state = this.graph.getView().getState(cell);
  var delta = null;

  if (state != null) {
    // Moves the animated state from where it will be after the model
    // change by subtracting the given delta vector from that location
    delta = this.getDelta(state);

    if (this.graph.getModel().isVertex(cell) && (delta.x != 0 || delta.y != 0)) {
      var translate = this.graph.view.getTranslate();
      var scale = this.graph.view.getScale();

      delta.x += translate.x * scale;
      delta.y += translate.y * scale;

      move.moveState(state, -delta.x / this.ease, -delta.y / this.ease);
    }
  }

  if (recurse && !this.stopRecursion(state, delta)) {
    var childCount = this.graph.getModel().getChildCount(cell);

    for (var i = 0; i < childCount; i++) {
      this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse);
    }
  }
};

/**
 * Function: stopRecursion
 *
 * Returns true if the animation should not recursively find more
 * deltas for children if the given parent state has been animated.
 */
mxMorphing.prototype.stopRecursion = function (state, delta) {
  return delta != null && (delta.x != 0 || delta.y != 0);
};

/**
 * Function: getDelta
 *
 * Returns the vector between the current rendered state and the future
 * location of the state after the display will be updated.
 */
mxMorphing.prototype.getDelta = function (state) {
  var origin = this.getOriginForCell(state.cell);
  var translate = this.graph.getView().getTranslate();
  var scale = this.graph.getView().getScale();
  var x = state.x / scale - translate.x;
  var y = state.y / scale - translate.y;

  return new mxPoint((origin.x - x) * scale, (origin.y - y) * scale);
};

/**
 * Function: getOriginForCell
 *
 * Returns the top, left corner of the given cell. TODO: Improve performance
 * by using caching inside this method as the result per cell never changes
 * during the lifecycle of this object.
 */
mxMorphing.prototype.getOriginForCell = function (cell) {
  var result = null;

  if (cell != null) {
    var parent = this.graph.getModel().getParent(cell);
    var geo = this.graph.getCellGeometry(cell);
    result = this.getOriginForCell(parent);

    // TODO: Handle offsets
    if (geo != null) {
      if (geo.relative) {
        var pgeo = this.graph.getCellGeometry(parent);

        if (pgeo != null) {
          result.x += geo.x * pgeo.width;
          result.y += geo.y * pgeo.height;
        }
      } else {
        result.x += geo.x;
        result.y += geo.y;
      }
    }
  }

  if (result == null) {
    var t = this.graph.view.getTranslate();
    result = new mxPoint(-t.x, -t.y);
  }

  return result;
};

/**
 * util/mxImageBundle.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxImageBundle
 *
 * Maps from keys to base64 encoded images or file locations. All values must
 * be URLs or use the format data:image/format followed by a comma and the base64
 * encoded image data, eg. "data:image/gif,XYZ", where XYZ is the base64 encoded
 * image data.
 *
 * To add a new image bundle to an existing graph, the following code is used:
 *
 * (code)
 * var bundle = new mxImageBundle(alt);
 * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' +
 *   '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' +
 *   'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' +
 *   'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback);
 * bundle.putImage('mySvgImage', 'data:image/svg+xml,' + encodeURIComponent(
 *   '<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">' +
 *   '<linearGradient id="gradient"><stop offset="10%" stop-color="#F00"/>' +
 *   '<stop offset="90%" stop-color="#fcc"/></linearGradient>' +
 *   '<rect fill="url(#gradient)" width="100%" height="100%"/></svg>'), fallback);
 * graph.addImageBundle(bundle);
 * (end);
 *
 * Alt is an optional boolean (default is false) that specifies if the value
 * or the fallback should be returned in <getImage>.
 *
 * The image can then be referenced in any cell style using image=myImage.
 * If you are using mxOutline, you should use the same image bundles in the
 * graph that renders the outline.
 *
 * The keys for images are resolved in <mxGraph.postProcessCellStyle> and
 * turned into a data URI if the returned value has a short data URI format
 * as specified above.
 *
 * A typical value for the fallback is a MTHML link as defined in RFC 2557.
 * Note that this format requires a file to be dynamically created on the
 * server-side, or the page that contains the graph to be modified to contain
 * the resources, this can be done by adding a comment that contains the
 * resource in the HEAD section of the page after the title tag.
 *
 * This type of fallback mechanism should be used in IE6 and IE7. IE8 does
 * support data URIs, but the maximum size is limited to 32 KB, which means
 * all data URIs should be limited to 32 KB.
 */
function mxImageBundle(alt) {
  this.images = [];
  this.alt = alt != null ? alt : false;
}

/**
 * Variable: images
 *
 * Maps from keys to images.
 */
mxImageBundle.prototype.images = null;

/**
 * Variable: alt
 *
 * Specifies if the fallback representation should be returned.
 */
mxImageBundle.prototype.alt = null;

/**
 * Function: putImage
 *
 * Adds the specified entry to the map. The entry is an object with a value and
 * fallback property as specified in the arguments.
 */
mxImageBundle.prototype.putImage = function (key, value, fallback) {
  this.images[key] = { value: value, fallback: fallback };
};

/**
 * Function: getImage
 *
 * Returns the value for the given key. This returns the value
 * or fallback, depending on <alt>. The fallback is returned if
 * <alt> is true, the value is returned otherwise.
 */
mxImageBundle.prototype.getImage = function (key) {
  var result = null;

  if (key != null) {
    var img = this.images[key];

    if (img != null) {
      result = this.alt ? img.fallback : img.value;
    }
  }

  return result;
};

/**
 * util/mxImageExport.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxImageExport
 *
 * Creates a new image export instance to be used with an export canvas. Here
 * is an example that uses this class to create an image via a backend using
 * <mxXmlExportCanvas>.
 *
 * (code)
 * var xmlDoc = mxUtils.createXmlDocument();
 * var root = xmlDoc.createElement('output');
 * xmlDoc.appendChild(root);
 *
 * var xmlCanvas = new mxXmlCanvas2D(root);
 * var imgExport = new mxImageExport();
 * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
 *
 * var bounds = graph.getGraphBounds();
 * var w = Math.ceil(bounds.x + bounds.width);
 * var h = Math.ceil(bounds.y + bounds.height);
 *
 * var xml = mxUtils.getXml(root);
 * new mxXmlRequest('export', 'format=png&w=' + w +
 * 		'&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))
 * 		.simulate(document, '_blank');
 * (end)
 *
 * Constructor: mxImageExport
 *
 * Constructs a new image export.
 */
function mxImageExport() {}

/**
 * Variable: includeOverlays
 *
 * Specifies if overlays should be included in the export. Default is false.
 */
mxImageExport.prototype.includeOverlays = false;

/**
 * Function: drawState
 *
 * Draws the given state and all its descendants to the given canvas.
 */
mxImageExport.prototype.drawState = function (state, canvas) {
  if (state != null) {
    this.visitStatesRecursive(
      state,
      canvas,
      mxUtils.bind(this, function () {
        this.drawCellState.apply(this, arguments);
      })
    );

    // Paints the overlays
    if (this.includeOverlays) {
      this.visitStatesRecursive(
        state,
        canvas,
        mxUtils.bind(this, function () {
          this.drawOverlays.apply(this, arguments);
        })
      );
    }
  }
};

/**
 * Function: visitStatesRecursive
 *
 * Visits the given state and all its descendants to the given canvas recursively.
 */
mxImageExport.prototype.visitStatesRecursive = function (state, canvas, visitor) {
  if (state != null) {
    visitor(state, canvas);

    var graph = state.view.graph;
    var childCount = graph.model.getChildCount(state.cell);

    for (var i = 0; i < childCount; i++) {
      var childState = graph.view.getState(graph.model.getChildAt(state.cell, i));
      this.visitStatesRecursive(childState, canvas, visitor);
    }
  }
};

/**
 * Function: getLinkForCellState
 *
 * Returns the link for the given cell state and canvas. This returns null.
 */
mxImageExport.prototype.getLinkForCellState = function (state, canvas) {
  return null;
};

/**
 * Function: drawCellState
 *
 * Draws the given state to the given canvas.
 */
mxImageExport.prototype.drawCellState = function (state, canvas) {
  // Experimental feature
  var link = this.getLinkForCellState(state, canvas);

  if (link != null) {
    canvas.setLink(link);
  }

  // Paints the shape and text
  this.drawShape(state, canvas);
  this.drawText(state, canvas);

  if (link != null) {
    canvas.setLink(null);
  }
};

/**
 * Function: drawShape
 *
 * Draws the shape of the given state.
 */
mxImageExport.prototype.drawShape = function (state, canvas) {
  if (state.shape instanceof mxShape && state.shape.checkBounds()) {
    canvas.save();

    state.shape.beforePaint(canvas);
    state.shape.paint(canvas);
    state.shape.afterPaint(canvas);

    canvas.restore();
  }
};

/**
 * Function: drawText
 *
 * Draws the text of the given state.
 */
mxImageExport.prototype.drawText = function (state, canvas) {
  if (state.text != null && state.text.checkBounds()) {
    canvas.save();

    state.text.beforePaint(canvas);
    state.text.paint(canvas);
    state.text.afterPaint(canvas);

    canvas.restore();
  }
};

/**
 * Function: drawOverlays
 *
 * Draws the overlays for the given state. This is called if <includeOverlays>
 * is true.
 */
mxImageExport.prototype.drawOverlays = function (state, canvas) {
  if (state.overlays != null) {
    state.overlays.visit(function (id, shape) {
      if (shape instanceof mxShape) {
        shape.paint(canvas);
      }
    });
  }
};

/**
 * util/mxAbstractCanvas2D.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxAbstractCanvas2D
 *
 * Base class for all canvases. A description of the public API is available in <mxXmlCanvas2D>.
 * All color values of <mxConstants.NONE> will be converted to null in the state.
 *
 * Constructor: mxAbstractCanvas2D
 *
 * Constructs a new abstract canvas.
 */
function mxAbstractCanvas2D() {
  /**
   * Variable: converter
   *
   * Holds the <mxUrlConverter> to convert image URLs.
   */
  this.converter = this.createUrlConverter();

  this.reset();
}

/**
 * Variable: state
 *
 * Holds the current state.
 */
mxAbstractCanvas2D.prototype.state = null;

/**
 * Variable: states
 *
 * Stack of states.
 */
mxAbstractCanvas2D.prototype.states = null;

/**
 * Variable: path
 *
 * Holds the current path as an array.
 */
mxAbstractCanvas2D.prototype.path = null;

/**
 * Variable: rotateHtml
 *
 * Switch for rotation of HTML. Default is false.
 */
mxAbstractCanvas2D.prototype.rotateHtml = true;

/**
 * Variable: lastX
 *
 * Holds the last x coordinate.
 */
mxAbstractCanvas2D.prototype.lastX = 0;

/**
 * Variable: lastY
 *
 * Holds the last y coordinate.
 */
mxAbstractCanvas2D.prototype.lastY = 0;

/**
 * Variable: moveOp
 *
 * Contains the string used for moving in paths. Default is 'M'.
 */
mxAbstractCanvas2D.prototype.moveOp = 'M';

/**
 * Variable: lineOp
 *
 * Contains the string used for moving in paths. Default is 'L'.
 */
mxAbstractCanvas2D.prototype.lineOp = 'L';

/**
 * Variable: quadOp
 *
 * Contains the string used for quadratic paths. Default is 'Q'.
 */
mxAbstractCanvas2D.prototype.quadOp = 'Q';

/**
 * Variable: curveOp
 *
 * Contains the string used for bezier curves. Default is 'C'.
 */
mxAbstractCanvas2D.prototype.curveOp = 'C';

/**
 * Variable: closeOp
 *
 * Holds the operator for closing curves. Default is 'Z'.
 */
mxAbstractCanvas2D.prototype.closeOp = 'Z';

/**
 * Variable: pointerEvents
 *
 * Boolean value that specifies if events should be handled. Default is false.
 */
mxAbstractCanvas2D.prototype.pointerEvents = false;

/**
 * Function: createUrlConverter
 *
 * Create a new <mxUrlConverter> and returns it.
 */
mxAbstractCanvas2D.prototype.createUrlConverter = function () {
  return new mxUrlConverter();
};

/**
 * Function: reset
 *
 * Resets the state of this canvas.
 */
mxAbstractCanvas2D.prototype.reset = function () {
  this.state = this.createState();
  this.states = [];
};

/**
 * Function: createState
 *
 * Creates the state of the this canvas.
 */
mxAbstractCanvas2D.prototype.createState = function () {
  return {
    dx: 0,
    dy: 0,
    scale: 1,
    alpha: 1,
    fillAlpha: 1,
    strokeAlpha: 1,
    fillColor: null,
    gradientFillAlpha: 1,
    gradientColor: null,
    gradientAlpha: 1,
    gradientDirection: null,
    strokeColor: null,
    strokeWidth: 1,
    dashed: false,
    dashPattern: '3 3',
    fixDash: false,
    lineCap: 'flat',
    lineJoin: 'miter',
    miterLimit: 10,
    fontColor: '#000000',
    fontBackgroundColor: null,
    fontBorderColor: null,
    fontSize: mxConstants.DEFAULT_FONTSIZE,
    fontFamily: mxConstants.DEFAULT_FONTFAMILY,
    fontStyle: 0,
    shadow: false,
    shadowColor: mxConstants.SHADOWCOLOR,
    shadowAlpha: mxConstants.SHADOW_OPACITY,
    shadowDx: mxConstants.SHADOW_OFFSET_X,
    shadowDy: mxConstants.SHADOW_OFFSET_Y,
    rotation: 0,
    rotationCx: 0,
    rotationCy: 0,
  };
};

/**
 * Function: format
 *
 * Rounds all numbers to integers.
 */
mxAbstractCanvas2D.prototype.format = function (value) {
  return Math.round(parseFloat(value));
};

/**
 * Function: addOp
 *
 * Adds the given operation to the path.
 */
mxAbstractCanvas2D.prototype.addOp = function () {
  if (this.path != null) {
    this.path.push(arguments[0]);

    if (arguments.length > 2) {
      var s = this.state;

      for (var i = 2; i < arguments.length; i += 2) {
        this.lastX = arguments[i - 1];
        this.lastY = arguments[i];

        this.path.push(this.format((this.lastX + s.dx) * s.scale));
        this.path.push(this.format((this.lastY + s.dy) * s.scale));
      }
    }
  }
};

/**
 * Function: rotatePoint
 *
 * Rotates the given point and returns the result as an <mxPoint>.
 */
mxAbstractCanvas2D.prototype.rotatePoint = function (x, y, theta, cx, cy) {
  var rad = theta * (Math.PI / 180);

  return mxUtils.getRotatedPoint(new mxPoint(x, y), Math.cos(rad), Math.sin(rad), new mxPoint(cx, cy));
};

/**
 * Function: save
 *
 * Saves the current state.
 */
mxAbstractCanvas2D.prototype.save = function () {
  this.states.push(this.state);
  this.state = mxUtils.clone(this.state);
};

/**
 * Function: restore
 *
 * Restores the current state.
 */
mxAbstractCanvas2D.prototype.restore = function () {
  if (this.states.length > 0) {
    this.state = this.states.pop();
  }
};

/**
 * Function: setLink
 *
 * Sets the current link. Hook for subclassers.
 */
mxAbstractCanvas2D.prototype.setLink = function (link) {
  // nop
};

/**
 * Function: scale
 *
 * Scales the current state.
 */
mxAbstractCanvas2D.prototype.scale = function (value) {
  this.state.scale *= value;
  this.state.strokeWidth *= value;
};

/**
 * Function: translate
 *
 * Translates the current state.
 */
mxAbstractCanvas2D.prototype.translate = function (dx, dy) {
  this.state.dx += dx;
  this.state.dy += dy;
};

/**
 * Function: rotate
 *
 * Rotates the current state.
 */
mxAbstractCanvas2D.prototype.rotate = function (theta, flipH, flipV, cx, cy) {
  // nop
};

/**
 * Function: setAlpha
 *
 * Sets the current alpha.
 */
mxAbstractCanvas2D.prototype.setAlpha = function (value) {
  this.state.alpha = value;
};

/**
 * Function: setFillAlpha
 *
 * Sets the current solid fill alpha.
 */
mxAbstractCanvas2D.prototype.setFillAlpha = function (value) {
  this.state.fillAlpha = value;
};

/**
 * Function: setStrokeAlpha
 *
 * Sets the current stroke alpha.
 */
mxAbstractCanvas2D.prototype.setStrokeAlpha = function (value) {
  this.state.strokeAlpha = value;
};

/**
 * Function: setFillColor
 *
 * Sets the current fill color.
 */
mxAbstractCanvas2D.prototype.setFillColor = function (value) {
  if (value == mxConstants.NONE) {
    value = null;
  }

  this.state.fillColor = value;
  this.state.gradientColor = null;
};

/**
 * Function: setGradient
 *
 * Sets the current gradient.
 */
mxAbstractCanvas2D.prototype.setGradient = function (color1, color2, x, y, w, h, direction, alpha1, alpha2) {
  var s = this.state;
  s.fillColor = color1;
  s.gradientFillAlpha = alpha1 != null ? alpha1 : 1;
  s.gradientColor = color2;
  s.gradientAlpha = alpha2 != null ? alpha2 : 1;
  s.gradientDirection = direction;
};

/**
 * Function: setStrokeColor
 *
 * Sets the current stroke color.
 */
mxAbstractCanvas2D.prototype.setStrokeColor = function (value) {
  if (value == mxConstants.NONE) {
    value = null;
  }

  this.state.strokeColor = value;
};

/**
 * Function: setStrokeWidth
 *
 * Sets the current stroke width.
 */
mxAbstractCanvas2D.prototype.setStrokeWidth = function (value) {
  this.state.strokeWidth = value;
};

/**
 * Function: setDashed
 *
 * Enables or disables dashed lines.
 */
mxAbstractCanvas2D.prototype.setDashed = function (value, fixDash) {
  this.state.dashed = value;
  this.state.fixDash = fixDash;
};

/**
 * Function: setDashPattern
 *
 * Sets the current dash pattern.
 */
mxAbstractCanvas2D.prototype.setDashPattern = function (value) {
  this.state.dashPattern = value;
};

/**
 * Function: setLineCap
 *
 * Sets the current line cap.
 */
mxAbstractCanvas2D.prototype.setLineCap = function (value) {
  this.state.lineCap = value;
};

/**
 * Function: setLineJoin
 *
 * Sets the current line join.
 */
mxAbstractCanvas2D.prototype.setLineJoin = function (value) {
  this.state.lineJoin = value;
};

/**
 * Function: setMiterLimit
 *
 * Sets the current miter limit.
 */
mxAbstractCanvas2D.prototype.setMiterLimit = function (value) {
  this.state.miterLimit = value;
};

/**
 * Function: setFontColor
 *
 * Sets the current font color.
 */
mxAbstractCanvas2D.prototype.setFontColor = function (value) {
  if (value == mxConstants.NONE) {
    value = null;
  }

  this.state.fontColor = value;
};

/**
 * Function: setFontBackgroundColor
 *
 * Sets the current font background color.
 */
mxAbstractCanvas2D.prototype.setFontBackgroundColor = function (value) {
  if (value == mxConstants.NONE) {
    value = null;
  }

  this.state.fontBackgroundColor = value;
};

/**
 * Function: setFontBorderColor
 *
 * Sets the current font border color.
 */
mxAbstractCanvas2D.prototype.setFontBorderColor = function (value) {
  if (value == mxConstants.NONE) {
    value = null;
  }

  this.state.fontBorderColor = value;
};

/**
 * Function: setFontSize
 *
 * Sets the current font size.
 */
mxAbstractCanvas2D.prototype.setFontSize = function (value) {
  this.state.fontSize = parseFloat(value);
};

/**
 * Function: setFontFamily
 *
 * Sets the current font family.
 */
mxAbstractCanvas2D.prototype.setFontFamily = function (value) {
  this.state.fontFamily = value;
};

/**
 * Function: setFontStyle
 *
 * Sets the current font style.
 */
mxAbstractCanvas2D.prototype.setFontStyle = function (value) {
  if (value == null) {
    value = 0;
  }

  this.state.fontStyle = value;
};

/**
 * Function: setShadow
 *
 * Enables or disables and configures the current shadow.
 */
mxAbstractCanvas2D.prototype.setShadow = function (enabled) {
  this.state.shadow = enabled;
};

/**
 * Function: setShadowColor
 *
 * Enables or disables and configures the current shadow.
 */
mxAbstractCanvas2D.prototype.setShadowColor = function (value) {
  if (value == mxConstants.NONE) {
    value = null;
  }

  this.state.shadowColor = value;
};

/**
 * Function: setShadowAlpha
 *
 * Enables or disables and configures the current shadow.
 */
mxAbstractCanvas2D.prototype.setShadowAlpha = function (value) {
  this.state.shadowAlpha = value;
};

/**
 * Function: setShadowOffset
 *
 * Enables or disables and configures the current shadow.
 */
mxAbstractCanvas2D.prototype.setShadowOffset = function (dx, dy) {
  this.state.shadowDx = dx;
  this.state.shadowDy = dy;
};

/**
 * Function: begin
 *
 * Starts a new path.
 */
mxAbstractCanvas2D.prototype.begin = function () {
  this.lastX = 0;
  this.lastY = 0;
  this.path = [];
};

/**
 * Function: moveTo
 *
 *  Moves the current path the given coordinates.
 */
mxAbstractCanvas2D.prototype.moveTo = function (x, y) {
  this.addOp(this.moveOp, x, y);
};

/**
 * Function: lineTo
 *
 * Draws a line to the given coordinates. Uses moveTo with the op argument.
 */
mxAbstractCanvas2D.prototype.lineTo = function (x, y) {
  this.addOp(this.lineOp, x, y);
};

/**
 * Function: quadTo
 *
 * Adds a quadratic curve to the current path.
 */
mxAbstractCanvas2D.prototype.quadTo = function (x1, y1, x2, y2) {
  this.addOp(this.quadOp, x1, y1, x2, y2);
};

/**
 * Function: curveTo
 *
 * Adds a bezier curve to the current path.
 */
mxAbstractCanvas2D.prototype.curveTo = function (x1, y1, x2, y2, x3, y3) {
  this.addOp(this.curveOp, x1, y1, x2, y2, x3, y3);
};

/**
 * Function: arcTo
 *
 * Adds the given arc to the current path. This is a synthetic operation that
 * is broken down into curves.
 */
mxAbstractCanvas2D.prototype.arcTo = function (rx, ry, angle, largeArcFlag, sweepFlag, x, y) {
  var curves = mxUtils.arcToCurves(this.lastX, this.lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y);

  if (curves != null) {
    for (var i = 0; i < curves.length; i += 6) {
      this.curveTo(curves[i], curves[i + 1], curves[i + 2], curves[i + 3], curves[i + 4], curves[i + 5]);
    }
  }
};

/**
 * Function: close
 *
 * Closes the current path.
 */
mxAbstractCanvas2D.prototype.close = function (x1, y1, x2, y2, x3, y3) {
  this.addOp(this.closeOp);
};

/**
 * Function: end
 *
 * Empty implementation for backwards compatibility. This will be removed.
 */
mxAbstractCanvas2D.prototype.end = function () {};

/**
 * util/mxXmlCanvas2D.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxXmlCanvas2D
 *
 * Base class for all canvases. The following methods make up the public
 * interface of the canvas 2D for all painting in mxGraph:
 *
 * - <save>, <restore>
 * - <scale>, <translate>, <rotate>
 * - <setAlpha>, <setFillAlpha>, <setStrokeAlpha>, <setFillColor>, <setGradient>,
 *   <setStrokeColor>, <setStrokeWidth>, <setDashed>, <setDashPattern>, <setLineCap>,
 *   <setLineJoin>, <setMiterLimit>
 * - <setFontColor>, <setFontBackgroundColor>, <setFontBorderColor>, <setFontSize>,
 *   <setFontFamily>, <setFontStyle>
 * - <setShadow>, <setShadowColor>, <setShadowAlpha>, <setShadowOffset>
 * - <rect>, <roundrect>, <ellipse>, <image>, <text>
 * - <begin>, <moveTo>, <lineTo>, <quadTo>, <curveTo>
 * - <stroke>, <fill>, <fillAndStroke>
 *
 * <mxAbstractCanvas2D.arcTo> is an additional method for drawing paths. This is
 * a synthetic method, meaning that it is turned into a sequence of curves by
 * default. Subclassers may add native support for arcs.
 *
 * Constructor: mxXmlCanvas2D
 *
 * Constructs a new abstract canvas.
 */
function mxXmlCanvas2D(root) {
  mxAbstractCanvas2D.call(this);

  /**
   * Variable: root
   *
   * Reference to the container for the SVG content.
   */
  this.root = root;

  // Writes default settings;
  this.writeDefaults();
}

/**
 * Extends mxAbstractCanvas2D
 */
mxUtils.extend(mxXmlCanvas2D, mxAbstractCanvas2D);

/**
 * Variable: textEnabled
 *
 * Specifies if text output should be enabled. Default is true.
 */
mxXmlCanvas2D.prototype.textEnabled = true;

/**
 * Variable: compressed
 *
 * Specifies if the output should be compressed by removing redundant calls.
 * Default is true.
 */
mxXmlCanvas2D.prototype.compressed = true;

/**
 * Function: writeDefaults
 *
 * Writes the rendering defaults to <root>:
 */
mxXmlCanvas2D.prototype.writeDefaults = function () {
  var elem;

  // Writes font defaults
  elem = this.createElement('fontfamily');
  elem.setAttribute('family', mxConstants.DEFAULT_FONTFAMILY);
  this.root.appendChild(elem);

  elem = this.createElement('fontsize');
  elem.setAttribute('size', mxConstants.DEFAULT_FONTSIZE);
  this.root.appendChild(elem);

  // Writes shadow defaults
  elem = this.createElement('shadowcolor');
  elem.setAttribute('color', mxConstants.SHADOWCOLOR);
  this.root.appendChild(elem);

  elem = this.createElement('shadowalpha');
  elem.setAttribute('alpha', mxConstants.SHADOW_OPACITY);
  this.root.appendChild(elem);

  elem = this.createElement('shadowoffset');
  elem.setAttribute('dx', mxConstants.SHADOW_OFFSET_X);
  elem.setAttribute('dy', mxConstants.SHADOW_OFFSET_Y);
  this.root.appendChild(elem);
};

/**
 * Function: format
 *
 * Returns a formatted number with 2 decimal places.
 */
mxXmlCanvas2D.prototype.format = function (value) {
  return parseFloat(parseFloat(value).toFixed(2));
};

/**
 * Function: createElement
 *
 * Creates the given element using the owner document of <root>.
 */
mxXmlCanvas2D.prototype.createElement = function (name) {
  return this.root.ownerDocument.createElement(name);
};

/**
 * Function: save
 *
 * Saves the drawing state.
 */
mxXmlCanvas2D.prototype.save = function () {
  if (this.compressed) {
    mxAbstractCanvas2D.prototype.save.apply(this, arguments);
  }

  this.root.appendChild(this.createElement('save'));
};

/**
 * Function: restore
 *
 * Restores the drawing state.
 */
mxXmlCanvas2D.prototype.restore = function () {
  if (this.compressed) {
    mxAbstractCanvas2D.prototype.restore.apply(this, arguments);
  }

  this.root.appendChild(this.createElement('restore'));
};

/**
 * Function: scale
 *
 * Scales the output.
 *
 * Parameters:
 *
 * scale - Number that represents the scale where 1 is equal to 100%.
 */
mxXmlCanvas2D.prototype.scale = function (value) {
  var elem = this.createElement('scale');
  elem.setAttribute('scale', value);
  this.root.appendChild(elem);
};

/**
 * Function: translate
 *
 * Translates the output.
 *
 * Parameters:
 *
 * dx - Number that specifies the horizontal translation.
 * dy - Number that specifies the vertical translation.
 */
mxXmlCanvas2D.prototype.translate = function (dx, dy) {
  var elem = this.createElement('translate');
  elem.setAttribute('dx', this.format(dx));
  elem.setAttribute('dy', this.format(dy));
  this.root.appendChild(elem);
};

/**
 * Function: rotate
 *
 * Rotates and/or flips the output around a given center. (Note: Due to
 * limitations in VML, the rotation cannot be concatenated.)
 *
 * Parameters:
 *
 * theta - Number that represents the angle of the rotation (in degrees).
 * flipH - Boolean indicating if the output should be flipped horizontally.
 * flipV - Boolean indicating if the output should be flipped vertically.
 * cx - Number that represents the x-coordinate of the rotation center.
 * cy - Number that represents the y-coordinate of the rotation center.
 */
mxXmlCanvas2D.prototype.rotate = function (theta, flipH, flipV, cx, cy) {
  var elem = this.createElement('rotate');

  if (theta != 0 || flipH || flipV) {
    elem.setAttribute('theta', this.format(theta));
    elem.setAttribute('flipH', flipH ? '1' : '0');
    elem.setAttribute('flipV', flipV ? '1' : '0');
    elem.setAttribute('cx', this.format(cx));
    elem.setAttribute('cy', this.format(cy));
    this.root.appendChild(elem);
  }
};

/**
 * Function: setAlpha
 *
 * Sets the current alpha.
 *
 * Parameters:
 *
 * value - Number that represents the new alpha. Possible values are between
 * 1 (opaque) and 0 (transparent).
 */
mxXmlCanvas2D.prototype.setAlpha = function (value) {
  if (this.compressed) {
    if (this.state.alpha == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setAlpha.apply(this, arguments);
  }

  var elem = this.createElement('alpha');
  elem.setAttribute('alpha', this.format(value));
  this.root.appendChild(elem);
};

/**
 * Function: setFillAlpha
 *
 * Sets the current fill alpha.
 *
 * Parameters:
 *
 * value - Number that represents the new fill alpha. Possible values are between
 * 1 (opaque) and 0 (transparent).
 */
mxXmlCanvas2D.prototype.setFillAlpha = function (value) {
  if (this.compressed) {
    if (this.state.fillAlpha == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setFillAlpha.apply(this, arguments);
  }

  var elem = this.createElement('fillalpha');
  elem.setAttribute('alpha', this.format(value));
  this.root.appendChild(elem);
};

/**
 * Function: setStrokeAlpha
 *
 * Sets the current stroke alpha.
 *
 * Parameters:
 *
 * value - Number that represents the new stroke alpha. Possible values are between
 * 1 (opaque) and 0 (transparent).
 */
mxXmlCanvas2D.prototype.setStrokeAlpha = function (value) {
  if (this.compressed) {
    if (this.state.strokeAlpha == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setStrokeAlpha.apply(this, arguments);
  }

  var elem = this.createElement('strokealpha');
  elem.setAttribute('alpha', this.format(value));
  this.root.appendChild(elem);
};

/**
 * Function: setFillColor
 *
 * Sets the current fill color.
 *
 * Parameters:
 *
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setFillColor = function (value) {
  if (value == mxConstants.NONE) {
    value = null;
  }

  if (this.compressed) {
    if (this.state.fillColor == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setFillColor.apply(this, arguments);
  }

  var elem = this.createElement('fillcolor');
  elem.setAttribute('color', value != null ? value : mxConstants.NONE);
  this.root.appendChild(elem);
};

/**
 * Function: setGradient
 *
 * Sets the gradient. Note that the coordinates may be ignored by some implementations.
 *
 * Parameters:
 *
 * color1 - Hexadecimal representation of the start color.
 * color2 - Hexadecimal representation of the end color.
 * x - X-coordinate of the gradient region.
 * y - y-coordinate of the gradient region.
 * w - Width of the gradient region.
 * h - Height of the gradient region.
 * direction - One of <mxConstants.DIRECTION_NORTH>, <mxConstants.DIRECTION_EAST>,
 * <mxConstants.DIRECTION_SOUTH> or <mxConstants.DIRECTION_WEST>.
 * alpha1 - Optional alpha of the start color. Default is 1. Possible values
 * are between 1 (opaque) and 0 (transparent).
 * alpha2 - Optional alpha of the end color. Default is 1. Possible values
 * are between 1 (opaque) and 0 (transparent).
 */
mxXmlCanvas2D.prototype.setGradient = function (color1, color2, x, y, w, h, direction, alpha1, alpha2) {
  if (color1 != null && color2 != null) {
    mxAbstractCanvas2D.prototype.setGradient.apply(this, arguments);

    var elem = this.createElement('gradient');
    elem.setAttribute('c1', color1);
    elem.setAttribute('c2', color2);
    elem.setAttribute('x', this.format(x));
    elem.setAttribute('y', this.format(y));
    elem.setAttribute('w', this.format(w));
    elem.setAttribute('h', this.format(h));

    // Default direction is south
    if (direction != null) {
      elem.setAttribute('direction', direction);
    }

    if (alpha1 != null) {
      elem.setAttribute('alpha1', alpha1);
    }

    if (alpha2 != null) {
      elem.setAttribute('alpha2', alpha2);
    }

    this.root.appendChild(elem);
  }
};

/**
 * Function: setStrokeColor
 *
 * Sets the current stroke color.
 *
 * Parameters:
 *
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setStrokeColor = function (value) {
  if (value == mxConstants.NONE) {
    value = null;
  }

  if (this.compressed) {
    if (this.state.strokeColor == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setStrokeColor.apply(this, arguments);
  }

  var elem = this.createElement('strokecolor');
  elem.setAttribute('color', value != null ? value : mxConstants.NONE);
  this.root.appendChild(elem);
};

/**
 * Function: setStrokeWidth
 *
 * Sets the current stroke width.
 *
 * Parameters:
 *
 * value - Numeric representation of the stroke width.
 */
mxXmlCanvas2D.prototype.setStrokeWidth = function (value) {
  if (this.compressed) {
    if (this.state.strokeWidth == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setStrokeWidth.apply(this, arguments);
  }

  var elem = this.createElement('strokewidth');
  elem.setAttribute('width', this.format(value));
  this.root.appendChild(elem);
};

/**
 * Function: setDashed
 *
 * Enables or disables dashed lines.
 *
 * Parameters:
 *
 * value - Boolean that specifies if dashed lines should be enabled.
 * value - Boolean that specifies if the stroke width should be ignored
 * for the dash pattern. Default is false.
 */
mxXmlCanvas2D.prototype.setDashed = function (value, fixDash) {
  if (this.compressed) {
    if (this.state.dashed == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setDashed.apply(this, arguments);
  }

  var elem = this.createElement('dashed');
  elem.setAttribute('dashed', value ? '1' : '0');

  if (fixDash != null) {
    elem.setAttribute('fixDash', fixDash ? '1' : '0');
  }

  this.root.appendChild(elem);
};

/**
 * Function: setDashPattern
 *
 * Sets the current dash pattern. Default is '3 3'.
 *
 * Parameters:
 *
 * value - String that represents the dash pattern, which is a sequence of
 * numbers defining the length of the dashes and the length of the spaces
 * between the dashes. The lengths are relative to the line width - a length
 * of 1 is equals to the line width.
 */
mxXmlCanvas2D.prototype.setDashPattern = function (value) {
  if (this.compressed) {
    if (this.state.dashPattern == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setDashPattern.apply(this, arguments);
  }

  var elem = this.createElement('dashpattern');
  elem.setAttribute('pattern', value);
  this.root.appendChild(elem);
};

/**
 * Function: setLineCap
 *
 * Sets the line cap. Default is 'flat' which corresponds to 'butt' in SVG.
 *
 * Parameters:
 *
 * value - String that represents the line cap. Possible values are flat, round
 * and square.
 */
mxXmlCanvas2D.prototype.setLineCap = function (value) {
  if (this.compressed) {
    if (this.state.lineCap == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setLineCap.apply(this, arguments);
  }

  var elem = this.createElement('linecap');
  elem.setAttribute('cap', value);
  this.root.appendChild(elem);
};

/**
 * Function: setLineJoin
 *
 * Sets the line join. Default is 'miter'.
 *
 * Parameters:
 *
 * value - String that represents the line join. Possible values are miter,
 * round and bevel.
 */
mxXmlCanvas2D.prototype.setLineJoin = function (value) {
  if (this.compressed) {
    if (this.state.lineJoin == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setLineJoin.apply(this, arguments);
  }

  var elem = this.createElement('linejoin');
  elem.setAttribute('join', value);
  this.root.appendChild(elem);
};

/**
 * Function: setMiterLimit
 *
 * Sets the miter limit. Default is 10.
 *
 * Parameters:
 *
 * value - Number that represents the miter limit.
 */
mxXmlCanvas2D.prototype.setMiterLimit = function (value) {
  if (this.compressed) {
    if (this.state.miterLimit == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setMiterLimit.apply(this, arguments);
  }

  var elem = this.createElement('miterlimit');
  elem.setAttribute('limit', value);
  this.root.appendChild(elem);
};

/**
 * Function: setFontColor
 *
 * Sets the current font color. Default is '#000000'.
 *
 * Parameters:
 *
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setFontColor = function (value) {
  if (this.textEnabled) {
    if (value == mxConstants.NONE) {
      value = null;
    }

    if (this.compressed) {
      if (this.state.fontColor == value) {
        return;
      }

      mxAbstractCanvas2D.prototype.setFontColor.apply(this, arguments);
    }

    var elem = this.createElement('fontcolor');
    elem.setAttribute('color', value != null ? value : mxConstants.NONE);
    this.root.appendChild(elem);
  }
};

/**
 * Function: setFontBackgroundColor
 *
 * Sets the current font background color.
 *
 * Parameters:
 *
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setFontBackgroundColor = function (value) {
  if (this.textEnabled) {
    if (value == mxConstants.NONE) {
      value = null;
    }

    if (this.compressed) {
      if (this.state.fontBackgroundColor == value) {
        return;
      }

      mxAbstractCanvas2D.prototype.setFontBackgroundColor.apply(this, arguments);
    }

    var elem = this.createElement('fontbackgroundcolor');
    elem.setAttribute('color', value != null ? value : mxConstants.NONE);
    this.root.appendChild(elem);
  }
};

/**
 * Function: setFontBorderColor
 *
 * Sets the current font border color.
 *
 * Parameters:
 *
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setFontBorderColor = function (value) {
  if (this.textEnabled) {
    if (value == mxConstants.NONE) {
      value = null;
    }

    if (this.compressed) {
      if (this.state.fontBorderColor == value) {
        return;
      }

      mxAbstractCanvas2D.prototype.setFontBorderColor.apply(this, arguments);
    }

    var elem = this.createElement('fontbordercolor');
    elem.setAttribute('color', value != null ? value : mxConstants.NONE);
    this.root.appendChild(elem);
  }
};

/**
 * Function: setFontSize
 *
 * Sets the current font size. Default is <mxConstants.DEFAULT_FONTSIZE>.
 *
 * Parameters:
 *
 * value - Numeric representation of the font size.
 */
mxXmlCanvas2D.prototype.setFontSize = function (value) {
  if (this.textEnabled) {
    if (this.compressed) {
      if (this.state.fontSize == value) {
        return;
      }

      mxAbstractCanvas2D.prototype.setFontSize.apply(this, arguments);
    }

    var elem = this.createElement('fontsize');
    elem.setAttribute('size', value);
    this.root.appendChild(elem);
  }
};

/**
 * Function: setFontFamily
 *
 * Sets the current font family. Default is <mxConstants.DEFAULT_FONTFAMILY>.
 *
 * Parameters:
 *
 * value - String representation of the font family. This handles the same
 * values as the CSS font-family property.
 */
mxXmlCanvas2D.prototype.setFontFamily = function (value) {
  if (this.textEnabled) {
    if (this.compressed) {
      if (this.state.fontFamily == value) {
        return;
      }

      mxAbstractCanvas2D.prototype.setFontFamily.apply(this, arguments);
    }

    var elem = this.createElement('fontfamily');
    elem.setAttribute('family', value);
    this.root.appendChild(elem);
  }
};

/**
 * Function: setFontStyle
 *
 * Sets the current font style.
 *
 * Parameters:
 *
 * value - Numeric representation of the font family. This is the sum of the
 * font styles from <mxConstants>.
 */
mxXmlCanvas2D.prototype.setFontStyle = function (value) {
  if (this.textEnabled) {
    if (value == null) {
      value = 0;
    }

    if (this.compressed) {
      if (this.state.fontStyle == value) {
        return;
      }

      mxAbstractCanvas2D.prototype.setFontStyle.apply(this, arguments);
    }

    var elem = this.createElement('fontstyle');
    elem.setAttribute('style', value);
    this.root.appendChild(elem);
  }
};

/**
 * Function: setShadow
 *
 * Enables or disables shadows.
 *
 * Parameters:
 *
 * value - Boolean that specifies if shadows should be enabled.
 */
mxXmlCanvas2D.prototype.setShadow = function (value) {
  if (this.compressed) {
    if (this.state.shadow == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setShadow.apply(this, arguments);
  }

  var elem = this.createElement('shadow');
  elem.setAttribute('enabled', value ? '1' : '0');
  this.root.appendChild(elem);
};

/**
 * Function: setShadowColor
 *
 * Sets the current shadow color. Default is <mxConstants.SHADOWCOLOR>.
 *
 * Parameters:
 *
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setShadowColor = function (value) {
  if (this.compressed) {
    if (value == mxConstants.NONE) {
      value = null;
    }

    if (this.state.shadowColor == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setShadowColor.apply(this, arguments);
  }

  var elem = this.createElement('shadowcolor');
  elem.setAttribute('color', value != null ? value : mxConstants.NONE);
  this.root.appendChild(elem);
};

/**
 * Function: setShadowAlpha
 *
 * Sets the current shadows alpha. Default is <mxConstants.SHADOW_OPACITY>.
 *
 * Parameters:
 *
 * value - Number that represents the new alpha. Possible values are between
 * 1 (opaque) and 0 (transparent).
 */
mxXmlCanvas2D.prototype.setShadowAlpha = function (value) {
  if (this.compressed) {
    if (this.state.shadowAlpha == value) {
      return;
    }

    mxAbstractCanvas2D.prototype.setShadowAlpha.apply(this, arguments);
  }

  var elem = this.createElement('shadowalpha');
  elem.setAttribute('alpha', value);
  this.root.appendChild(elem);
};

/**
 * Function: setShadowOffset
 *
 * Sets the current shadow offset.
 *
 * Parameters:
 *
 * dx - Number that represents the horizontal offset of the shadow.
 * dy - Number that represents the vertical offset of the shadow.
 */
mxXmlCanvas2D.prototype.setShadowOffset = function (dx, dy) {
  if (this.compressed) {
    if (this.state.shadowDx == dx && this.state.shadowDy == dy) {
      return;
    }

    mxAbstractCanvas2D.prototype.setShadowOffset.apply(this, arguments);
  }

  var elem = this.createElement('shadowoffset');
  elem.setAttribute('dx', dx);
  elem.setAttribute('dy', dy);
  this.root.appendChild(elem);
};

/**
 * Function: rect
 *
 * Puts a rectangle into the drawing buffer.
 *
 * Parameters:
 *
 * x - Number that represents the x-coordinate of the rectangle.
 * y - Number that represents the y-coordinate of the rectangle.
 * w - Number that represents the width of the rectangle.
 * h - Number that represents the height of the rectangle.
 */
mxXmlCanvas2D.prototype.rect = function (x, y, w, h) {
  var elem = this.createElement('rect');
  elem.setAttribute('x', this.format(x));
  elem.setAttribute('y', this.format(y));
  elem.setAttribute('w', this.format(w));
  elem.setAttribute('h', this.format(h));
  this.root.appendChild(elem);
};

/**
 * Function: roundrect
 *
 * Puts a rounded rectangle into the drawing buffer.
 *
 * Parameters:
 *
 * x - Number that represents the x-coordinate of the rectangle.
 * y - Number that represents the y-coordinate of the rectangle.
 * w - Number that represents the width of the rectangle.
 * h - Number that represents the height of the rectangle.
 * dx - Number that represents the horizontal rounding.
 * dy - Number that represents the vertical rounding.
 */
mxXmlCanvas2D.prototype.roundrect = function (x, y, w, h, dx, dy) {
  var elem = this.createElement('roundrect');
  elem.setAttribute('x', this.format(x));
  elem.setAttribute('y', this.format(y));
  elem.setAttribute('w', this.format(w));
  elem.setAttribute('h', this.format(h));
  elem.setAttribute('dx', this.format(dx));
  elem.setAttribute('dy', this.format(dy));
  this.root.appendChild(elem);
};

/**
 * Function: ellipse
 *
 * Puts an ellipse into the drawing buffer.
 *
 * Parameters:
 *
 * x - Number that represents the x-coordinate of the ellipse.
 * y - Number that represents the y-coordinate of the ellipse.
 * w - Number that represents the width of the ellipse.
 * h - Number that represents the height of the ellipse.
 */
mxXmlCanvas2D.prototype.ellipse = function (x, y, w, h) {
  var elem = this.createElement('ellipse');
  elem.setAttribute('x', this.format(x));
  elem.setAttribute('y', this.format(y));
  elem.setAttribute('w', this.format(w));
  elem.setAttribute('h', this.format(h));
  this.root.appendChild(elem);
};

/**
 * Function: image
 *
 * Paints an image.
 *
 * Parameters:
 *
 * x - Number that represents the x-coordinate of the image.
 * y - Number that represents the y-coordinate of the image.
 * w - Number that represents the width of the image.
 * h - Number that represents the height of the image.
 * src - String that specifies the URL of the image.
 * aspect - Boolean indicating if the aspect of the image should be preserved.
 * flipH - Boolean indicating if the image should be flipped horizontally.
 * flipV - Boolean indicating if the image should be flipped vertically.
 */
mxXmlCanvas2D.prototype.image = function (x, y, w, h, src, aspect, flipH, flipV) {
  src = this.converter.convert(src);

  // LATER: Add option for embedding images as base64.
  var elem = this.createElement('image');
  elem.setAttribute('x', this.format(x));
  elem.setAttribute('y', this.format(y));
  elem.setAttribute('w', this.format(w));
  elem.setAttribute('h', this.format(h));
  elem.setAttribute('src', src);
  elem.setAttribute('aspect', aspect ? '1' : '0');
  elem.setAttribute('flipH', flipH ? '1' : '0');
  elem.setAttribute('flipV', flipV ? '1' : '0');
  this.root.appendChild(elem);
};

/**
 * Function: begin
 *
 * Starts a new path and puts it into the drawing buffer.
 */
mxXmlCanvas2D.prototype.begin = function () {
  this.root.appendChild(this.createElement('begin'));
  this.lastX = 0;
  this.lastY = 0;
};

/**
 * Function: moveTo
 *
 * Moves the current path the given point.
 *
 * Parameters:
 *
 * x - Number that represents the x-coordinate of the point.
 * y - Number that represents the y-coordinate of the point.
 */
mxXmlCanvas2D.prototype.moveTo = function (x, y) {
  var elem = this.createElement('move');
  elem.setAttribute('x', this.format(x));
  elem.setAttribute('y', this.format(y));
  this.root.appendChild(elem);
  this.lastX = x;
  this.lastY = y;
};

/**
 * Function: lineTo
 *
 * Draws a line to the given coordinates.
 *
 * Parameters:
 *
 * x - Number that represents the x-coordinate of the endpoint.
 * y - Number that represents the y-coordinate of the endpoint.
 */
mxXmlCanvas2D.prototype.lineTo = function (x, y) {
  var elem = this.createElement('line');
  elem.setAttribute('x', this.format(x));
  elem.setAttribute('y', this.format(y));
  this.root.appendChild(elem);
  this.lastX = x;
  this.lastY = y;
};

/**
 * Function: quadTo
 *
 * Adds a quadratic curve to the current path.
 *
 * Parameters:
 *
 * x1 - Number that represents the x-coordinate of the control point.
 * y1 - Number that represents the y-coordinate of the control point.
 * x2 - Number that represents the x-coordinate of the endpoint.
 * y2 - Number that represents the y-coordinate of the endpoint.
 */
mxXmlCanvas2D.prototype.quadTo = function (x1, y1, x2, y2) {
  var elem = this.createElement('quad');
  elem.setAttribute('x1', this.format(x1));
  elem.setAttribute('y1', this.format(y1));
  elem.setAttribute('x2', this.format(x2));
  elem.setAttribute('y2', this.format(y2));
  this.root.appendChild(elem);
  this.lastX = x2;
  this.lastY = y2;
};

/**
 * Function: curveTo
 *
 * Adds a bezier curve to the current path.
 *
 * Parameters:
 *
 * x1 - Number that represents the x-coordinate of the first control point.
 * y1 - Number that represents the y-coordinate of the first control point.
 * x2 - Number that represents the x-coordinate of the second control point.
 * y2 - Number that represents the y-coordinate of the second control point.
 * x3 - Number that represents the x-coordinate of the endpoint.
 * y3 - Number that represents the y-coordinate of the endpoint.
 */
mxXmlCanvas2D.prototype.curveTo = function (x1, y1, x2, y2, x3, y3) {
  var elem = this.createElement('curve');
  elem.setAttribute('x1', this.format(x1));
  elem.setAttribute('y1', this.format(y1));
  elem.setAttribute('x2', this.format(x2));
  elem.setAttribute('y2', this.format(y2));
  elem.setAttribute('x3', this.format(x3));
  elem.setAttribute('y3', this.format(y3));
  this.root.appendChild(elem);
  this.lastX = x3;
  this.lastY = y3;
};

/**
 * Function: close
 *
 * Closes the current path.
 */
mxXmlCanvas2D.prototype.close = function () {
  this.root.appendChild(this.createElement('close'));
};

/**
 * Function: text
 *
 * Paints the given text. Possible values for format are empty string for
 * plain text and html for HTML markup. Background and border color as well
 * as clipping is not available in plain text labels for VML. HTML labels
 * are not available as part of shapes with no foreignObject support in SVG
 * (eg. IE9, IE10).
 *
 * Parameters:
 *
 * x - Number that represents the x-coordinate of the text.
 * y - Number that represents the y-coordinate of the text.
 * w - Number that represents the available width for the text or 0 for automatic width.
 * h - Number that represents the available height for the text or 0 for automatic height.
 * str - String that specifies the text to be painted.
 * align - String that represents the horizontal alignment.
 * valign - String that represents the vertical alignment.
 * wrap - Boolean that specifies if word-wrapping is enabled. Requires w > 0.
 * format - Empty string for plain text or 'html' for HTML markup.
 * overflow - Specifies the overflow behaviour of the label. Requires w > 0 and/or h > 0.
 * clip - Boolean that specifies if the label should be clipped. Requires w > 0 and/or h > 0.
 * rotation - Number that specifies the angle of the rotation around the anchor point of the text.
 * dir - Optional string that specifies the text direction. Possible values are rtl and lrt.
 */
mxXmlCanvas2D.prototype.text = function (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) {
  if (this.textEnabled && str != null) {
    if (mxUtils.isNode(str)) {
      str = mxUtils.getOuterHtml(str);
    }

    var elem = this.createElement('text');
    elem.setAttribute('x', this.format(x));
    elem.setAttribute('y', this.format(y));
    elem.setAttribute('w', this.format(w));
    elem.setAttribute('h', this.format(h));
    elem.setAttribute('str', str);

    if (align != null) {
      elem.setAttribute('align', align);
    }

    if (valign != null) {
      elem.setAttribute('valign', valign);
    }

    elem.setAttribute('wrap', wrap ? '1' : '0');

    if (format == null) {
      format = '';
    }

    elem.setAttribute('format', format);

    if (overflow != null) {
      elem.setAttribute('overflow', overflow);
    }

    if (clip != null) {
      elem.setAttribute('clip', clip ? '1' : '0');
    }

    if (rotation != null) {
      elem.setAttribute('rotation', rotation);
    }

    if (dir != null) {
      elem.setAttribute('dir', dir);
    }

    this.root.appendChild(elem);
  }
};

/**
 * Function: stroke
 *
 * Paints the outline of the current drawing buffer.
 */
mxXmlCanvas2D.prototype.stroke = function () {
  this.root.appendChild(this.createElement('stroke'));
};

/**
 * Function: fill
 *
 * Fills the current drawing buffer.
 */
mxXmlCanvas2D.prototype.fill = function () {
  this.root.appendChild(this.createElement('fill'));
};

/**
 * Function: fillAndStroke
 *
 * Fills the current drawing buffer and its outline.
 */
mxXmlCanvas2D.prototype.fillAndStroke = function () {
  this.root.appendChild(this.createElement('fillstroke'));
};

/**
 * util/mxSvgCanvas2D.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxSvgCanvas2D
 *
 * Extends <mxAbstractCanvas2D> to implement a canvas for SVG. This canvas writes all
 * calls as SVG output to the given SVG root node.
 *
 * (code)
 * var svgDoc = mxUtils.createXmlDocument();
 * var root = (svgDoc.createElementNS != null) ?
 * 		svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg');
 *
 * if (svgDoc.createElementNS == null)
 * {
 *   root.setAttribute('xmlns', mxConstants.NS_SVG);
 *   root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);
 * }
 * else
 * {
 *   root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);
 * }
 *
 * var bounds = graph.getGraphBounds();
 * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px');
 * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px');
 * root.setAttribute('version', '1.1');
 *
 * svgDoc.appendChild(root);
 *
 * var svgCanvas = new mxSvgCanvas2D(root);
 * (end)
 *
 * A description of the public API is available in <mxXmlCanvas2D>.
 *
 * To disable anti-aliasing in the output, use the following code.
 *
 * (code)
 * graph.view.canvas.ownerSVGElement.setAttribute('shape-rendering', 'crispEdges');
 * (end)
 *
 * Or set the respective attribute in the SVG element directly.
 *
 * Constructor: mxSvgCanvas2D
 *
 * Constructs a new SVG canvas.
 *
 * Parameters:
 *
 * root - SVG container for the output.
 * styleEnabled - Optional boolean that specifies if a style section should be
 * added. The style section sets the default font-size, font-family and
 * stroke-miterlimit globally. Default is false.
 */
function mxSvgCanvas2D(root, styleEnabled) {
  mxAbstractCanvas2D.call(this);

  /**
   * Variable: root
   *
   * Reference to the container for the SVG content.
   */
  this.root = root;

  /**
   * Variable: gradients
   *
   * Local cache of gradients for quick lookups.
   */
  this.gradients = [];

  /**
   * Variable: defs
   *
   * Reference to the defs section of the SVG document. Only for export.
   */
  this.defs = null;

  /**
   * Variable: styleEnabled
   *
   * Stores the value of styleEnabled passed to the constructor.
   */
  this.styleEnabled = styleEnabled != null ? styleEnabled : false;

  var svg = null;

  // Adds optional defs section for export
  if (root.ownerDocument != document) {
    var node = root;

    // Finds owner SVG element in XML DOM
    while (node != null && node.nodeName != 'svg') {
      node = node.parentNode;
    }

    svg = node;
  }

  if (svg != null) {
    // Tries to get existing defs section
    var tmp = svg.getElementsByTagName('defs');

    if (tmp.length > 0) {
      this.defs = svg.getElementsByTagName('defs')[0];
    }

    // Adds defs section if none exists
    if (this.defs == null) {
      this.defs = this.createElement('defs');

      if (svg.firstChild != null) {
        svg.insertBefore(this.defs, svg.firstChild);
      } else {
        svg.appendChild(this.defs);
      }
    }

    // Adds stylesheet
    if (this.styleEnabled) {
      this.defs.appendChild(this.createStyle());
    }
  }
}

/**
 * Extends mxAbstractCanvas2D
 */
mxUtils.extend(mxSvgCanvas2D, mxAbstractCanvas2D);

/**
 * Capability check for DOM parser and checks if base tag is used.
 */
(function () {
  mxSvgCanvas2D.prototype.useDomParser =
    !mxClient.IS_IE && typeof DOMParser === 'function' && typeof XMLSerializer === 'function';

  if (mxSvgCanvas2D.prototype.useDomParser) {
    // Checks using a generic test text if the parsing actually works. This is a workaround
    // for older browsers where the capability check returns true but the parsing fails.
    try {
      var doc = new DOMParser().parseFromString('test text', 'text/html');
      mxSvgCanvas2D.prototype.useDomParser = doc != null;
    } catch (e) {
      mxSvgCanvas2D.prototype.useDomParser = false;
    }
  }

  // Activates workaround for gradient ID resolution if base tag is used.
  mxSvgCanvas2D.prototype.useAbsoluteIds =
    !mxClient.IS_CHROMEAPP &&
    !mxClient.IS_IE &&
    !mxClient.IS_IE11 &&
    !mxClient.IS_EDGE &&
    document.getElementsByTagName('base').length > 0;
})();

/**
 * Variable: path
 *
 * Holds the current DOM node.
 */
mxSvgCanvas2D.prototype.node = null;

/**
 * Variable: matchHtmlAlignment
 *
 * Specifies if plain text output should match the vertical HTML alignment.
 * Defaul is true.
 */
mxSvgCanvas2D.prototype.matchHtmlAlignment = true;

/**
 * Variable: textEnabled
 *
 * Specifies if text output should be enabled. Default is true.
 */
mxSvgCanvas2D.prototype.textEnabled = true;

/**
 * Variable: foEnabled
 *
 * Specifies if use of foreignObject for HTML markup is allowed. Default is true.
 */
mxSvgCanvas2D.prototype.foEnabled = true;

/**
 * Variable: foAltText
 *
 * Specifies the fallback text for unsupported foreignObjects in exported
 * documents. Default is '[Object]'. If this is set to null then no fallback
 * text is added to the exported document.
 */
mxSvgCanvas2D.prototype.foAltText = '[Object]';

/**
 * Variable: foOffset
 *
 * Offset to be used for foreignObjects.
 */
mxSvgCanvas2D.prototype.foOffset = 0;

/**
 * Variable: textOffset
 *
 * Offset to be used for text elements.
 */
mxSvgCanvas2D.prototype.textOffset = 0;

/**
 * Variable: imageOffset
 *
 * Offset to be used for image elements.
 */
mxSvgCanvas2D.prototype.imageOffset = 0;

/**
 * Variable: strokeTolerance
 *
 * Adds transparent paths for strokes.
 */
mxSvgCanvas2D.prototype.strokeTolerance = 0;

/**
 * Variable: minStrokeWidth
 *
 * Minimum stroke width for output.
 */
mxSvgCanvas2D.prototype.minStrokeWidth = 1;

/**
 * Variable: refCount
 *
 * Local counter for references in SVG export.
 */
mxSvgCanvas2D.prototype.refCount = 0;

/**
 * Variable: lineHeightCorrection
 *
 * Correction factor for <mxConstants.LINE_HEIGHT> in HTML output. Default is 1.
 */
mxSvgCanvas2D.prototype.lineHeightCorrection = 1;

/**
 * Variable: pointerEventsValue
 *
 * Default value for active pointer events. Default is all.
 */
mxSvgCanvas2D.prototype.pointerEventsValue = 'all';

/**
 * Variable: fontMetricsPadding
 *
 * Padding to be added for text that is not wrapped to account for differences
 * in font metrics on different platforms in pixels. Default is 10.
 */
mxSvgCanvas2D.prototype.fontMetricsPadding = 10;

/**
 * Variable: cacheOffsetSize
 *
 * Specifies if offsetWidth and offsetHeight should be cached. Default is true.
 * This is used to speed up repaint of text in <updateText>.
 */
mxSvgCanvas2D.prototype.cacheOffsetSize = true;

/**
 * Function: format
 *
 * Rounds all numbers to 2 decimal points.
 */
mxSvgCanvas2D.prototype.format = function (value) {
  return parseFloat(parseFloat(value).toFixed(2));
};

/**
 * Function: getBaseUrl
 *
 * Returns the URL of the page without the hash part. This needs to use href to
 * include any search part with no params (ie question mark alone). This is a
 * workaround for the fact that window.location.search is empty if there is
 * no search string behind the question mark.
 */
mxSvgCanvas2D.prototype.getBaseUrl = function () {
  var href = window.location.href;
  var hash = href.lastIndexOf('#');

  if (hash > 0) {
    href = href.substring(0, hash);
  }

  return href;
};

/**
 * Function: reset
 *
 * Returns any offsets for rendering pixels.
 */
mxSvgCanvas2D.prototype.reset = function () {
  mxAbstractCanvas2D.prototype.reset.apply(this, arguments);
  this.gradients = [];
};

/**
 * Function: createStyle
 *
 * Creates the optional style section.
 */
mxSvgCanvas2D.prototype.createStyle = function (x) {
  var style = this.createElement('style');
  style.setAttribute('type', 'text/css');
  mxUtils.write(
    style,
    'svg{font-family:' +
      mxConstants.DEFAULT_FONTFAMILY +
      ';font-size:' +
      mxConstants.DEFAULT_FONTSIZE +
      ';fill:none;stroke-miterlimit:10}'
  );

  return style;
};

/**
 * Function: createElement
 *
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.createElement = function (tagName, namespace) {
  if (this.root.ownerDocument.createElementNS != null) {
    return this.root.ownerDocument.createElementNS(namespace || mxConstants.NS_SVG, tagName);
  } else {
    var elt = this.root.ownerDocument.createElement(tagName);

    if (namespace != null) {
      elt.setAttribute('xmlns', namespace);
    }

    return elt;
  }
};

/**
 * Function: getAlternateText
 *
 * Returns the alternate text string for the given foreignObject.
 */
mxSvgCanvas2D.prototype.getAlternateText = function (
  fo,
  x,
  y,
  w,
  h,
  str,
  align,
  valign,
  wrap,
  format,
  overflow,
  clip,
  rotation
) {
  return str != null ? this.foAltText : null;
};

/**
 * Function: getAlternateContent
 *
 * Returns the alternate content for the given foreignObject.
 */
mxSvgCanvas2D.prototype.createAlternateContent = function (
  fo,
  x,
  y,
  w,
  h,
  str,
  align,
  valign,
  wrap,
  format,
  overflow,
  clip,
  rotation
) {
  var text = this.getAlternateText(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
  var s = this.state;

  if (text != null && s.fontSize > 0) {
    var dy = valign == mxConstants.ALIGN_TOP ? 1 : valign == mxConstants.ALIGN_BOTTOM ? 0 : 0.3;
    var anchor = align == mxConstants.ALIGN_RIGHT ? 'end' : align == mxConstants.ALIGN_LEFT ? 'start' : 'middle';

    var alt = this.createElement('text');
    alt.setAttribute('x', Math.round(x + s.dx));
    alt.setAttribute('y', Math.round(y + s.dy + dy * s.fontSize));
    alt.setAttribute('fill', s.fontColor || 'black');
    alt.setAttribute('font-family', s.fontFamily);
    alt.setAttribute('font-size', Math.round(s.fontSize) + 'px');

    // Text-anchor start is default in SVG
    if (anchor != 'start') {
      alt.setAttribute('text-anchor', anchor);
    }

    if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
      alt.setAttribute('font-weight', 'bold');
    }

    if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
      alt.setAttribute('font-style', 'italic');
    }

    var txtDecor = [];

    if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
      txtDecor.push('underline');
    }

    if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) {
      txtDecor.push('line-through');
    }

    if (txtDecor.length > 0) {
      alt.setAttribute('text-decoration', txtDecor.join(' '));
    }

    mxUtils.write(alt, text);

    return alt;
  } else {
    return null;
  }
};

/**
 * Function: createGradientId
 *
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.createGradientId = function (start, end, alpha1, alpha2, direction) {
  // Removes illegal characters from gradient ID
  if (start.charAt(0) == '#') {
    start = start.substring(1);
  }

  if (end.charAt(0) == '#') {
    end = end.substring(1);
  }

  // Workaround for gradient IDs not working in Safari 5 / Chrome 6
  // if they contain uppercase characters
  start = start.toLowerCase() + '-' + alpha1;
  end = end.toLowerCase() + '-' + alpha2;

  // Wrong gradient directions possible?
  var dir = null;

  if (direction == null || direction == mxConstants.DIRECTION_SOUTH) {
    dir = 's';
  } else if (direction == mxConstants.DIRECTION_EAST) {
    dir = 'e';
  } else {
    var tmp = start;
    start = end;
    end = tmp;

    if (direction == mxConstants.DIRECTION_NORTH) {
      dir = 's';
    } else if (direction == mxConstants.DIRECTION_WEST) {
      dir = 'e';
    }
  }

  return 'mx-gradient-' + start + '-' + end + '-' + dir;
};

/**
 * Function: getSvgGradient
 *
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.getSvgGradient = function (start, end, alpha1, alpha2, direction) {
  var id = this.createGradientId(start, end, alpha1, alpha2, direction);
  var gradient = this.gradients[id];

  if (gradient == null) {
    var svg = this.root.ownerSVGElement;

    var counter = 0;
    var tmpId = id + '-' + counter;

    if (svg != null) {
      gradient = svg.ownerDocument.getElementById(tmpId);

      while (gradient != null && gradient.ownerSVGElement != svg) {
        tmpId = id + '-' + counter++;
        gradient = svg.ownerDocument.getElementById(tmpId);
      }
    } else {
      // Uses shorter IDs for export
      tmpId = 'id' + ++this.refCount;
    }

    if (gradient == null) {
      gradient = this.createSvgGradient(start, end, alpha1, alpha2, direction);
      gradient.setAttribute('id', tmpId);

      if (this.defs != null) {
        this.defs.appendChild(gradient);
      } else {
        svg.appendChild(gradient);
      }
    }

    this.gradients[id] = gradient;
  }

  return gradient.getAttribute('id');
};

/**
 * Function: createSvgGradient
 *
 * Creates the given SVG gradient.
 */
mxSvgCanvas2D.prototype.createSvgGradient = function (start, end, alpha1, alpha2, direction) {
  var gradient = this.createElement('linearGradient');
  gradient.setAttribute('x1', '0%');
  gradient.setAttribute('y1', '0%');
  gradient.setAttribute('x2', '0%');
  gradient.setAttribute('y2', '0%');

  if (direction == null || direction == mxConstants.DIRECTION_SOUTH) {
    gradient.setAttribute('y2', '100%');
  } else if (direction == mxConstants.DIRECTION_EAST) {
    gradient.setAttribute('x2', '100%');
  } else if (direction == mxConstants.DIRECTION_NORTH) {
    gradient.setAttribute('y1', '100%');
  } else if (direction == mxConstants.DIRECTION_WEST) {
    gradient.setAttribute('x1', '100%');
  }

  var op = alpha1 < 1 ? ';stop-opacity:' + alpha1 : '';

  var stop = this.createElement('stop');
  stop.setAttribute('offset', '0%');
  stop.setAttribute('style', 'stop-color:' + start + op);
  gradient.appendChild(stop);

  op = alpha2 < 1 ? ';stop-opacity:' + alpha2 : '';

  stop = this.createElement('stop');
  stop.setAttribute('offset', '100%');
  stop.setAttribute('style', 'stop-color:' + end + op);
  gradient.appendChild(stop);

  return gradient;
};

/**
 * Function: addNode
 *
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.addNode = function (filled, stroked) {
  var node = this.node;
  var s = this.state;

  if (node != null) {
    if (node.nodeName == 'path') {
      // Checks if the path is not empty
      if (this.path != null && this.path.length > 0) {
        node.setAttribute('d', this.path.join(' '));
      } else {
        return;
      }
    }

    if (filled && s.fillColor != null) {
      this.updateFill();
    } else if (!this.styleEnabled) {
      // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=814952
      if (node.nodeName == 'ellipse' && mxClient.IS_FF) {
        node.setAttribute('fill', 'transparent');
      } else {
        node.setAttribute('fill', 'none');
      }

      // Sets the actual filled state for stroke tolerance
      filled = false;
    }

    if (stroked && s.strokeColor != null) {
      this.updateStroke();
    } else if (!this.styleEnabled) {
      node.setAttribute('stroke', 'none');
    }

    if (s.transform != null && s.transform.length > 0) {
      node.setAttribute('transform', s.transform);
    }

    if (s.shadow) {
      this.root.appendChild(this.createShadow(node));
    }

    // Adds stroke tolerance
    if (this.strokeTolerance > 0 && !filled) {
      this.root.appendChild(this.createTolerance(node));
    }

    // Adds pointer events
    if (this.pointerEvents) {
      node.setAttribute('pointer-events', this.pointerEventsValue);
    }
    // Enables clicks for nodes inside a link element
    else if (!this.pointerEvents && this.originalRoot == null) {
      node.setAttribute('pointer-events', 'none');
    }

    // Removes invisible nodes from output if they don't handle events
    if (
      (node.nodeName != 'rect' && node.nodeName != 'path' && node.nodeName != 'ellipse') ||
      (node.getAttribute('fill') != 'none' && node.getAttribute('fill') != 'transparent') ||
      node.getAttribute('stroke') != 'none' ||
      node.getAttribute('pointer-events') != 'none'
    ) {
      // LATER: Update existing DOM for performance
      this.root.appendChild(node);
    }

    this.node = null;
  }
};

/**
 * Function: updateFill
 *
 * Transfers the stroke attributes from <state> to <node>.
 */
mxSvgCanvas2D.prototype.updateFill = function () {
  var s = this.state;

  if (s.alpha < 1 || s.fillAlpha < 1) {
    this.node.setAttribute('fill-opacity', s.alpha * s.fillAlpha);
  }

  if (s.fillColor != null) {
    if (s.gradientColor != null) {
      var id = this.getSvgGradient(
        String(s.fillColor),
        String(s.gradientColor),
        s.gradientFillAlpha,
        s.gradientAlpha,
        s.gradientDirection
      );

      if (this.root.ownerDocument == document && this.useAbsoluteIds) {
        // Workaround for no fill with base tag in page (escape brackets)
        var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
        this.node.setAttribute('fill', 'url(' + base + '#' + id + ')');
      } else {
        this.node.setAttribute('fill', 'url(#' + id + ')');
      }
    } else {
      this.node.setAttribute('fill', String(s.fillColor).toLowerCase());
    }
  }
};

/**
 * Function: getCurrentStrokeWidth
 *
 * Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)).
 */
mxSvgCanvas2D.prototype.getCurrentStrokeWidth = function () {
  return Math.max(this.minStrokeWidth, Math.max(0.01, this.format(this.state.strokeWidth * this.state.scale)));
};

/**
 * Function: updateStroke
 *
 * Transfers the stroke attributes from <state> to <node>.
 */
mxSvgCanvas2D.prototype.updateStroke = function () {
  var s = this.state;

  this.node.setAttribute('stroke', String(s.strokeColor).toLowerCase());

  if (s.alpha < 1 || s.strokeAlpha < 1) {
    this.node.setAttribute('stroke-opacity', s.alpha * s.strokeAlpha);
  }

  var sw = this.getCurrentStrokeWidth();

  if (sw != 1) {
    this.node.setAttribute('stroke-width', sw);
  }

  if (this.node.nodeName == 'path') {
    this.updateStrokeAttributes();
  }

  if (s.dashed) {
    this.node.setAttribute('stroke-dasharray', this.createDashPattern((s.fixDash ? 1 : s.strokeWidth) * s.scale));
  }
};

/**
 * Function: updateStrokeAttributes
 *
 * Transfers the stroke attributes from <state> to <node>.
 */
mxSvgCanvas2D.prototype.updateStrokeAttributes = function () {
  var s = this.state;

  // Linejoin miter is default in SVG
  if (s.lineJoin != null && s.lineJoin != 'miter') {
    this.node.setAttribute('stroke-linejoin', s.lineJoin);
  }

  if (s.lineCap != null) {
    // flat is called butt in SVG
    var value = s.lineCap;

    if (value == 'flat') {
      value = 'butt';
    }

    // Linecap butt is default in SVG
    if (value != 'butt') {
      this.node.setAttribute('stroke-linecap', value);
    }
  }

  // Miterlimit 10 is default in our document
  if (s.miterLimit != null && (!this.styleEnabled || s.miterLimit != 10)) {
    this.node.setAttribute('stroke-miterlimit', s.miterLimit);
  }
};

/**
 * Function: createDashPattern
 *
 * Creates the SVG dash pattern for the given state.
 */
mxSvgCanvas2D.prototype.createDashPattern = function (scale) {
  var pat = [];

  if (typeof this.state.dashPattern === 'string') {
    var dash = this.state.dashPattern.split(' ');

    if (dash.length > 0) {
      for (var i = 0; i < dash.length; i++) {
        pat[i] = Number(dash[i]) * scale;
      }
    }
  }

  return pat.join(' ');
};

/**
 * Function: createTolerance
 *
 * Creates a hit detection tolerance shape for the given node.
 */
mxSvgCanvas2D.prototype.createTolerance = function (node) {
  var tol = node.cloneNode(true);
  var sw = parseFloat(tol.getAttribute('stroke-width') || 1) + this.strokeTolerance;
  tol.setAttribute('pointer-events', 'stroke');
  tol.setAttribute('visibility', 'hidden');
  tol.removeAttribute('stroke-dasharray');
  tol.setAttribute('stroke-width', sw);
  tol.setAttribute('fill', 'none');

  // Workaround for Opera ignoring the visiblity attribute above while
  // other browsers need a stroke color to perform the hit-detection but
  // do not ignore the visibility attribute. Side-effect is that Opera's
  // hit detection for horizontal/vertical edges seems to ignore the tol.
  tol.setAttribute('stroke', mxClient.IS_OT ? 'none' : 'white');

  return tol;
};

/**
 * Function: createShadow
 *
 * Creates a shadow for the given node.
 */
mxSvgCanvas2D.prototype.createShadow = function (node) {
  var shadow = node.cloneNode(true);
  var s = this.state;

  // Firefox uses transparent for no fill in ellipses
  if (shadow.getAttribute('fill') != 'none' && (!mxClient.IS_FF || shadow.getAttribute('fill') != 'transparent')) {
    shadow.setAttribute('fill', s.shadowColor);
  }

  if (shadow.getAttribute('stroke') != 'none') {
    shadow.setAttribute('stroke', s.shadowColor);
  }

  shadow.setAttribute(
    'transform',
    'translate(' +
      this.format(s.shadowDx * s.scale) +
      ',' +
      this.format(s.shadowDy * s.scale) +
      ')' +
      (s.transform || '')
  );
  shadow.setAttribute('opacity', s.shadowAlpha);

  return shadow;
};

/**
 * Function: setLink
 *
 * Experimental implementation for hyperlinks.
 */
mxSvgCanvas2D.prototype.setLink = function (link) {
  if (link == null) {
    this.root = this.originalRoot;
  } else {
    this.originalRoot = this.root;

    var node = this.createElement('a');

    // Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below
    // in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.
    if (node.setAttributeNS == null || (this.root.ownerDocument != document && document.documentMode == null)) {
      node.setAttribute('xlink:href', link);
    } else {
      node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', link);
    }

    this.root.appendChild(node);
    this.root = node;
  }
};

/**
 * Function: rotate
 *
 * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
 */
mxSvgCanvas2D.prototype.rotate = function (theta, flipH, flipV, cx, cy) {
  if (theta != 0 || flipH || flipV) {
    var s = this.state;
    cx += s.dx;
    cy += s.dy;

    cx *= s.scale;
    cy *= s.scale;

    s.transform = s.transform || '';

    // This implementation uses custom scale/translate and built-in rotation
    // Rotation state is part of the AffineTransform in state.transform
    if (flipH && flipV) {
      theta += 180;
    } else if (flipH != flipV) {
      var tx = flipH ? cx : 0;
      var sx = flipH ? -1 : 1;

      var ty = flipV ? cy : 0;
      var sy = flipV ? -1 : 1;

      s.transform +=
        'translate(' +
        this.format(tx) +
        ',' +
        this.format(ty) +
        ')' +
        'scale(' +
        this.format(sx) +
        ',' +
        this.format(sy) +
        ')' +
        'translate(' +
        this.format(-tx) +
        ',' +
        this.format(-ty) +
        ')';
    }

    if (flipH ? !flipV : flipV) {
      theta *= -1;
    }

    if (theta != 0) {
      s.transform += 'rotate(' + this.format(theta) + ',' + this.format(cx) + ',' + this.format(cy) + ')';
    }

    s.rotation = s.rotation + theta;
    s.rotationCx = cx;
    s.rotationCy = cy;
  }
};

/**
 * Function: begin
 *
 * Extends superclass to create path.
 */
mxSvgCanvas2D.prototype.begin = function () {
  mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
  this.node = this.createElement('path');
};

/**
 * Function: rect
 *
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.rect = function (x, y, w, h) {
  var s = this.state;
  var n = this.createElement('rect');
  n.setAttribute('x', this.format((x + s.dx) * s.scale));
  n.setAttribute('y', this.format((y + s.dy) * s.scale));
  n.setAttribute('width', this.format(w * s.scale));
  n.setAttribute('height', this.format(h * s.scale));

  this.node = n;
};

/**
 * Function: roundrect
 *
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.roundrect = function (x, y, w, h, dx, dy) {
  this.rect(x, y, w, h);

  if (dx > 0) {
    this.node.setAttribute('rx', this.format(dx * this.state.scale));
  }

  if (dy > 0) {
    this.node.setAttribute('ry', this.format(dy * this.state.scale));
  }
};

/**
 * Function: ellipse
 *
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.ellipse = function (x, y, w, h) {
  var s = this.state;
  var n = this.createElement('ellipse');
  // No rounding for consistent output with 1.x
  n.setAttribute('cx', this.format((x + w / 2 + s.dx) * s.scale));
  n.setAttribute('cy', this.format((y + h / 2 + s.dy) * s.scale));
  n.setAttribute('rx', (w / 2) * s.scale);
  n.setAttribute('ry', (h / 2) * s.scale);
  this.node = n;
};

/**
 * Function: image
 *
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.image = function (x, y, w, h, src, aspect, flipH, flipV) {
  src = this.converter.convert(src);

  // LATER: Add option for embedding images as base64.
  aspect = aspect != null ? aspect : true;
  flipH = flipH != null ? flipH : false;
  flipV = flipV != null ? flipV : false;

  var s = this.state;
  x += s.dx;
  y += s.dy;

  var node = this.createElement('image');
  node.setAttribute('x', this.format(x * s.scale) + this.imageOffset);
  node.setAttribute('y', this.format(y * s.scale) + this.imageOffset);
  node.setAttribute('width', this.format(w * s.scale));
  node.setAttribute('height', this.format(h * s.scale));

  // Workaround for missing namespace support
  if (node.setAttributeNS == null) {
    node.setAttribute('xlink:href', src);
  } else {
    node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
  }

  if (!aspect) {
    node.setAttribute('preserveAspectRatio', 'none');
  }

  if (s.alpha < 1 || s.fillAlpha < 1) {
    node.setAttribute('opacity', s.alpha * s.fillAlpha);
  }

  var tr = this.state.transform || '';

  if (flipH || flipV) {
    var sx = 1;
    var sy = 1;
    var dx = 0;
    var dy = 0;

    if (flipH) {
      sx = -1;
      dx = -w - 2 * x;
    }

    if (flipV) {
      sy = -1;
      dy = -h - 2 * y;
    }

    // Adds image tansformation to existing transform
    tr += 'scale(' + sx + ',' + sy + ')translate(' + dx * s.scale + ',' + dy * s.scale + ')';
  }

  if (tr.length > 0) {
    node.setAttribute('transform', tr);
  }

  if (!this.pointerEvents) {
    node.setAttribute('pointer-events', 'none');
  }

  this.root.appendChild(node);
};

/**
 * Function: convertHtml
 *
 * Converts the given HTML string to XHTML.
 */
mxSvgCanvas2D.prototype.convertHtml = function (val) {
  if (this.useDomParser) {
    var doc = new DOMParser().parseFromString(val, 'text/html');

    if (doc != null) {
      val = new XMLSerializer().serializeToString(doc.body);

      // Extracts body content from DOM
      if (val.substring(0, 5) == '<body') {
        val = val.substring(val.indexOf('>', 5) + 1);
      }

      if (val.substring(val.length - 7, val.length) == '</body>') {
        val = val.substring(0, val.length - 7);
      }
    }
  } else if (document.implementation != null && document.implementation.createDocument != null) {
    var xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
    var xb = xd.createElement('body');
    xd.documentElement.appendChild(xb);

    var div = document.createElement('div');
    div.innerHTML = val;
    var child = div.firstChild;

    while (child != null) {
      var next = child.nextSibling;
      xb.appendChild(xd.adoptNode(child));
      child = next;
    }

    return xb.innerHTML;
  } else {
    var ta = document.createElement('textarea');

    // Handles special HTML entities < and > and double escaping
    // and converts unclosed br, hr and img tags to XHTML
    // LATER: Convert all unclosed tags
    ta.innerHTML = val
      .replace(/&amp;/g, '&amp;amp;')
      .replace(/&#60;/g, '&amp;lt;')
      .replace(/&#62;/g, '&amp;gt;')
      .replace(/&lt;/g, '&amp;lt;')
      .replace(/&gt;/g, '&amp;gt;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;');
    val = ta.value
      .replace(/&/g, '&amp;')
      .replace(/&amp;lt;/g, '&lt;')
      .replace(/&amp;gt;/g, '&gt;')
      .replace(/&amp;amp;/g, '&amp;')
      .replace(/<br>/g, '<br />')
      .replace(/<hr>/g, '<hr />')
      .replace(/(<img[^>]+)>/gm, '$1 />');
  }

  return val;
};

/**
 * Function: createDiv
 *
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.createDiv = function (str) {
  var val = str;

  if (!mxUtils.isNode(val)) {
    val = '<div><div>' + this.convertHtml(val) + '</div></div>';
  }

  // IE uses this code for export as it cannot render foreignObjects
  if (!mxClient.IS_IE && !mxClient.IS_IE11 && document.createElementNS) {
    var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');

    if (mxUtils.isNode(val)) {
      var div2 = document.createElement('div');
      var div3 = div2.cloneNode(false);

      // Creates a copy for export
      if (this.root.ownerDocument != document) {
        div2.appendChild(val.cloneNode(true));
      } else {
        div2.appendChild(val);
      }

      div3.appendChild(div2);
      div.appendChild(div3);
    } else {
      div.innerHTML = val;
    }

    return div;
  } else {
    if (mxUtils.isNode(val)) {
      val = '<div><div>' + mxUtils.getXml(val) + '</div></div>';
    }

    val = '<div xmlns="http://www.w3.org/1999/xhtml">' + val + '</div>';

    // NOTE: FF 3.6 crashes if content CSS contains "height:100%"
    return mxUtils.parseXml(val).documentElement;
  }
};

/**
 * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below.
 */
mxSvgCanvas2D.prototype.updateText = function (x, y, w, h, align, valign, wrap, overflow, clip, rotation, node) {
  if (node != null && node.firstChild != null && node.firstChild.firstChild != null) {
    this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node.firstChild);
  }
};

/**
 * Function: addForeignObject
 *
 * Creates a foreignObject for the given string and adds it to the given root.
 */
mxSvgCanvas2D.prototype.addForeignObject = function (
  x,
  y,
  w,
  h,
  str,
  align,
  valign,
  wrap,
  format,
  overflow,
  clip,
  rotation,
  dir,
  div,
  root
) {
  var group = this.createElement('g');
  var fo = this.createElement('foreignObject');

  // Workarounds for print clipping and static position in Safari
  fo.setAttribute('style', 'overflow: visible; text-align: left;');
  fo.setAttribute('pointer-events', 'none');

  // Import needed for older versions of IE
  if (div.ownerDocument != document) {
    div = mxUtils.importNodeImplementation(fo.ownerDocument, div, true);
  }

  fo.appendChild(div);
  group.appendChild(fo);

  this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, group);

  // Alternate content if foreignObject not supported
  if (this.root.ownerDocument != document) {
    var alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);

    if (alt != null) {
      fo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility');
      var sw = this.createElement('switch');
      sw.appendChild(fo);
      sw.appendChild(alt);
      group.appendChild(sw);
    }
  }

  root.appendChild(group);
};

/**
 * Updates existing DOM nodes for text rendering.
 */
mxSvgCanvas2D.prototype.updateTextNodes = function (x, y, w, h, align, valign, wrap, overflow, clip, rotation, g) {
  var s = this.state.scale;

  mxSvgCanvas2D.createCss(
    w + 2,
    h,
    align,
    valign,
    wrap,
    overflow,
    clip,
    this.state.fontBackgroundColor != null ? this.state.fontBackgroundColor : null,
    this.state.fontBorderColor != null ? this.state.fontBorderColor : null,
    'display: flex; align-items: unsafe ' +
      (valign == mxConstants.ALIGN_TOP ? 'flex-start' : valign == mxConstants.ALIGN_BOTTOM ? 'flex-end' : 'center') +
      '; ' +
      'justify-content: unsafe ' +
      (align == mxConstants.ALIGN_LEFT ? 'flex-start' : align == mxConstants.ALIGN_RIGHT ? 'flex-end' : 'center') +
      '; ',
    this.getTextCss(),
    s,
    mxUtils.bind(this, function (dx, dy, flex, item, block) {
      x += this.state.dx;
      y += this.state.dy;

      var fo = g.firstChild;
      var div = fo.firstChild;
      var box = div.firstChild;
      var text = box.firstChild;
      var r = (this.rotateHtml ? this.state.rotation : 0) + (rotation != null ? rotation : 0);
      var t =
        (this.foOffset != 0 ? 'translate(' + this.foOffset + ' ' + this.foOffset + ')' : '') +
        (s != 1 ? 'scale(' + s + ')' : '');

      text.setAttribute('style', block);
      box.setAttribute('style', item);

      // Workaround for clipping in Webkit with scrolling and zoom
      fo.setAttribute('width', Math.ceil((1 / Math.min(1, s)) * 100) + '%');
      fo.setAttribute('height', Math.ceil((1 / Math.min(1, s)) * 100) + '%');
      var yp = Math.round(y + dy);

      // Allows for negative values which are causing problems with
      // transformed content where the top edge of the foreignObject
      // limits the text box being moved further up in the diagram.
      // KNOWN: Possible clipping problems with zoom and scrolling
      // but this is normally not used with scrollbars as the
      // coordinates are always positive with scrollbars.
      // Margin-top is ignored in Safari and no negative values allowed
      // for padding.
      if (yp < 0) {
        fo.setAttribute('y', yp);
      } else {
        fo.removeAttribute('y');
        flex += 'padding-top: ' + yp + 'px; ';
      }

      div.setAttribute('style', flex + 'margin-left: ' + Math.round(x + dx) + 'px;');
      t += r != 0 ? 'rotate(' + r + ' ' + x + ' ' + y + ')' : '';

      // Output allows for reflow but Safari cannot use absolute position,
      // transforms or opacity. https://bugs.webkit.org/show_bug.cgi?id=23113
      if (t != '') {
        g.setAttribute('transform', t);
      } else {
        g.removeAttribute('transform');
      }

      if (this.state.alpha != 1) {
        g.setAttribute('opacity', this.state.alpha);
      } else {
        g.removeAttribute('opacity');
      }
    })
  );
};

/**
 * Updates existing DOM nodes for text rendering.
 */
mxSvgCanvas2D.createCss = function (w, h, align, valign, wrap, overflow, clip, bg, border, flex, block, s, callback) {
  var item =
    'box-sizing: border-box; font-size: 0; text-align: ' +
    (align == mxConstants.ALIGN_LEFT ? 'left' : align == mxConstants.ALIGN_RIGHT ? 'right' : 'center') +
    '; ';
  var pt = mxUtils.getAlignmentAsPoint(align, valign);
  var ofl = 'overflow: hidden; ';
  var fw = 'width: 1px; ';
  var fh = 'height: 1px; ';
  var dx = pt.x * w;
  var dy = pt.y * h;

  if (clip) {
    fw = 'width: ' + Math.round(w) + 'px; ';
    item += 'max-height: ' + Math.round(h) + 'px; ';
    dy = 0;
  } else if (overflow == 'fill') {
    fw = 'width: ' + Math.round(w) + 'px; ';
    fh = 'height: ' + Math.round(h) + 'px; ';
    block += 'width: 100%; height: 100%; ';
    item += fw + fh;
  } else if (overflow == 'width') {
    fw = 'width: ' + Math.round(w) + 'px; ';
    block += 'width: 100%; ';
    item += fw;
    dy = 0;

    if (h > 0) {
      item += 'max-height: ' + Math.round(h) + 'px; ';
    }
  } else {
    ofl = '';
    dy = 0;
  }

  var bgc = '';

  if (bg != null) {
    bgc += 'background-color: ' + bg + '; ';
  }

  if (border != null) {
    bgc += 'border: 1px solid ' + border + '; ';
  }

  if (ofl == '' || clip) {
    block += bgc;
  } else {
    item += bgc;
  }

  if (wrap && w > 0) {
    block += 'white-space: normal; word-wrap: ' + mxConstants.WORD_WRAP + '; ';
    fw = 'width: ' + Math.round(w) + 'px; ';

    if (ofl != '' && overflow != 'fill') {
      dy = 0;
    }
  } else {
    block += 'white-space: nowrap; ';

    if (ofl == '') {
      dx = 0;
    }
  }

  callback(dx, dy, flex + fw + fh, item + ofl, block, ofl);
};

/**
 * Function: getTextCss
 *
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.getTextCss = function () {
  var s = this.state;
  var lh = mxConstants.ABSOLUTE_LINE_HEIGHT
    ? s.fontSize * mxConstants.LINE_HEIGHT + 'px'
    : mxConstants.LINE_HEIGHT * this.lineHeightCorrection;
  var css =
    'display: inline-block; font-size: ' +
    s.fontSize +
    'px; ' +
    'font-family: ' +
    s.fontFamily +
    '; color: ' +
    s.fontColor +
    '; line-height: ' +
    lh +
    '; pointer-events: ' +
    (this.pointerEvents ? this.pointerEventsValue : 'none') +
    '; ';

  if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
    css += 'font-weight: bold; ';
  }

  if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
    css += 'font-style: italic; ';
  }

  var deco = [];

  if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
    deco.push('underline');
  }

  if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) {
    deco.push('line-through');
  }

  if (deco.length > 0) {
    css += 'text-decoration: ' + deco.join(' ') + '; ';
  }

  return css;
};

/**
 * Function: text
 *
 * Paints the given text. Possible values for format are empty string for plain
 * text and html for HTML markup. Note that HTML markup is only supported if
 * foreignObject is supported and <foEnabled> is true. (This means IE9 and later
 * does currently not support HTML text as part of shapes.)
 */
mxSvgCanvas2D.prototype.text = function (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) {
  if (this.textEnabled && str != null) {
    rotation = rotation != null ? rotation : 0;

    if (this.foEnabled && format == 'html') {
      var div = this.createDiv(str);

      // Ignores invalid XHTML labels
      if (div != null) {
        if (dir != null) {
          div.setAttribute('dir', dir);
        }

        this.addForeignObject(
          x,
          y,
          w,
          h,
          str,
          align,
          valign,
          wrap,
          format,
          overflow,
          clip,
          rotation,
          dir,
          div,
          this.root
        );
      }
    } else {
      this.plainText(
        x + this.state.dx,
        y + this.state.dy,
        w,
        h,
        str,
        align,
        valign,
        wrap,
        overflow,
        clip,
        rotation,
        dir
      );
    }
  }
};

/**
 * Function: createClip
 *
 * Creates a clip for the given coordinates.
 */
mxSvgCanvas2D.prototype.createClip = function (x, y, w, h) {
  x = Math.round(x);
  y = Math.round(y);
  w = Math.round(w);
  h = Math.round(h);

  var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h;

  var counter = 0;
  var tmp = id + '-' + counter;

  // Resolves ID conflicts
  while (document.getElementById(tmp) != null) {
    tmp = id + '-' + ++counter;
  }

  clip = this.createElement('clipPath');
  clip.setAttribute('id', tmp);

  var rect = this.createElement('rect');
  rect.setAttribute('x', x);
  rect.setAttribute('y', y);
  rect.setAttribute('width', w);
  rect.setAttribute('height', h);

  clip.appendChild(rect);

  return clip;
};

/**
 * Function: plainText
 *
 * Paints the given text. Possible values for format are empty string for
 * plain text and html for HTML markup.
 */
mxSvgCanvas2D.prototype.plainText = function (x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir) {
  rotation = rotation != null ? rotation : 0;
  var s = this.state;
  var size = s.fontSize;
  var node = this.createElement('g');
  var tr = s.transform || '';
  this.updateFont(node);

  // Ignores pointer events
  if (!this.pointerEvents && this.originalRoot == null) {
    node.setAttribute('pointer-events', 'none');
  }

  // Non-rotated text
  if (rotation != 0) {
    tr += 'rotate(' + rotation + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')';
  }

  if (dir != null) {
    node.setAttribute('direction', dir);
  }

  if (clip && w > 0 && h > 0) {
    var cx = x;
    var cy = y;

    if (align == mxConstants.ALIGN_CENTER) {
      cx -= w / 2;
    } else if (align == mxConstants.ALIGN_RIGHT) {
      cx -= w;
    }

    if (overflow != 'fill') {
      if (valign == mxConstants.ALIGN_MIDDLE) {
        cy -= h / 2;
      } else if (valign == mxConstants.ALIGN_BOTTOM) {
        cy -= h;
      }
    }

    // LATER: Remove spacing from clip rectangle
    var c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4);

    if (this.defs != null) {
      this.defs.appendChild(c);
    } else {
      // Makes sure clip is removed with referencing node
      this.root.appendChild(c);
    }

    if (
      !mxClient.IS_CHROMEAPP &&
      !mxClient.IS_IE &&
      !mxClient.IS_IE11 &&
      !mxClient.IS_EDGE &&
      this.root.ownerDocument == document
    ) {
      // Workaround for potential base tag
      var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
      node.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')');
    } else {
      node.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')');
    }
  }

  // Default is left
  var anchor = align == mxConstants.ALIGN_RIGHT ? 'end' : align == mxConstants.ALIGN_CENTER ? 'middle' : 'start';

  // Text-anchor start is default in SVG
  if (anchor != 'start') {
    node.setAttribute('text-anchor', anchor);
  }

  if (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE) {
    node.setAttribute('font-size', size * s.scale + 'px');
  }

  if (tr.length > 0) {
    node.setAttribute('transform', tr);
  }

  if (s.alpha < 1) {
    node.setAttribute('opacity', s.alpha);
  }

  var lines = str.split('\n');
  var lh = Math.round(size * mxConstants.LINE_HEIGHT);
  var textHeight = size + (lines.length - 1) * lh;

  var cy = y + size - 1;

  if (valign == mxConstants.ALIGN_MIDDLE) {
    if (overflow == 'fill') {
      cy -= h / 2;
    } else {
      var dy = (this.matchHtmlAlignment && clip && h > 0 ? Math.min(textHeight, h) : textHeight) / 2;
      cy -= dy;
    }
  } else if (valign == mxConstants.ALIGN_BOTTOM) {
    if (overflow == 'fill') {
      cy -= h;
    } else {
      var dy = this.matchHtmlAlignment && clip && h > 0 ? Math.min(textHeight, h) : textHeight;
      cy -= dy + 1;
    }
  }

  for (var i = 0; i < lines.length; i++) {
    // Workaround for bounding box of empty lines and spaces
    if (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0) {
      var text = this.createElement('text');
      // LATER: Match horizontal HTML alignment
      text.setAttribute('x', this.format(x * s.scale) + this.textOffset);
      text.setAttribute('y', this.format(cy * s.scale) + this.textOffset);

      mxUtils.write(text, lines[i]);
      node.appendChild(text);
    }

    cy += lh;
  }

  this.root.appendChild(node);
  this.addTextBackground(node, str, x, y, w, overflow == 'fill' ? h : textHeight, align, valign, overflow);
};

/**
 * Function: updateFont
 *
 * Updates the text properties for the given node. (NOTE: For this to work in
 * IE, the given node must be a text or tspan element.)
 */
mxSvgCanvas2D.prototype.updateFont = function (node) {
  var s = this.state;

  node.setAttribute('fill', s.fontColor);

  if (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY) {
    node.setAttribute('font-family', s.fontFamily);
  }

  if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
    node.setAttribute('font-weight', 'bold');
  }

  if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
    node.setAttribute('font-style', 'italic');
  }

  var txtDecor = [];

  if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
    txtDecor.push('underline');
  }

  if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) {
    txtDecor.push('line-through');
  }

  if (txtDecor.length > 0) {
    node.setAttribute('text-decoration', txtDecor.join(' '));
  }
};

/**
 * Function: addTextBackground
 *
 * Background color and border
 */
mxSvgCanvas2D.prototype.addTextBackground = function (node, str, x, y, w, h, align, valign, overflow) {
  var s = this.state;

  if (s.fontBackgroundColor != null || s.fontBorderColor != null) {
    var bbox = null;

    if (overflow == 'fill' || overflow == 'width') {
      if (align == mxConstants.ALIGN_CENTER) {
        x -= w / 2;
      } else if (align == mxConstants.ALIGN_RIGHT) {
        x -= w;
      }

      if (valign == mxConstants.ALIGN_MIDDLE) {
        y -= h / 2;
      } else if (valign == mxConstants.ALIGN_BOTTOM) {
        y -= h;
      }

      bbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale);
    } else if (node.getBBox != null && this.root.ownerDocument == document) {
      // Uses getBBox only if inside document for correct size
      try {
        bbox = node.getBBox();
        var ie = mxClient.IS_IE && mxClient.IS_SVG;
        bbox = new mxRectangle(bbox.x, bbox.y + (ie ? 0 : 1), bbox.width, bbox.height + (ie ? 1 : 0));
      } catch (e) {
        // Ignores NS_ERROR_FAILURE in FF if container display is none.
      }
    }

    if (bbox == null || bbox.width == 0 || bbox.height == 0) {
      // Computes size if not in document or no getBBox available
      var div = document.createElement('div');

      // Wrapping and clipping can be ignored here
      div.style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT
        ? s.fontSize * mxConstants.LINE_HEIGHT + 'px'
        : mxConstants.LINE_HEIGHT;
      div.style.fontSize = s.fontSize + 'px';
      div.style.fontFamily = s.fontFamily;
      div.style.whiteSpace = 'nowrap';
      div.style.position = 'absolute';
      div.style.visibility = 'hidden';
      div.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block';
      div.style.zoom = '1';

      if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
        div.style.fontWeight = 'bold';
      }

      if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
        div.style.fontStyle = 'italic';
      }

      str = mxUtils.htmlEntities(str, false);
      div.innerHTML = str.replace(/\n/g, '<br/>');

      document.body.appendChild(div);
      var w = div.offsetWidth;
      var h = div.offsetHeight;
      div.parentNode.removeChild(div);

      if (align == mxConstants.ALIGN_CENTER) {
        x -= w / 2;
      } else if (align == mxConstants.ALIGN_RIGHT) {
        x -= w;
      }

      if (valign == mxConstants.ALIGN_MIDDLE) {
        y -= h / 2;
      } else if (valign == mxConstants.ALIGN_BOTTOM) {
        y -= h;
      }

      bbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale);
    }

    if (bbox != null) {
      var n = this.createElement('rect');
      n.setAttribute('fill', s.fontBackgroundColor || 'none');
      n.setAttribute('stroke', s.fontBorderColor || 'none');
      n.setAttribute('x', Math.floor(bbox.x - 1));
      n.setAttribute('y', Math.floor(bbox.y - 1));
      n.setAttribute('width', Math.ceil(bbox.width + 2));
      n.setAttribute('height', Math.ceil(bbox.height));

      var sw = s.fontBorderColor != null ? Math.max(1, this.format(s.scale)) : 0;
      n.setAttribute('stroke-width', sw);

      // Workaround for crisp rendering - only required if not exporting
      if (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1) {
        n.setAttribute('transform', 'translate(0.5, 0.5)');
      }

      node.insertBefore(n, node.firstChild);
    }
  }
};

/**
 * Function: stroke
 *
 * Paints the outline of the current path.
 */
mxSvgCanvas2D.prototype.stroke = function () {
  this.addNode(false, true);
};

/**
 * Function: fill
 *
 * Fills the current path.
 */
mxSvgCanvas2D.prototype.fill = function () {
  this.addNode(true, false);
};

/**
 * Function: fillAndStroke
 *
 * Fills and paints the outline of the current path.
 */
mxSvgCanvas2D.prototype.fillAndStroke = function () {
  this.addNode(true, true);
};

/**
 * util/mxVmlCanvas2D.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 *
 * Class: mxVmlCanvas2D
 *
 * Implements a canvas to be used for rendering VML. Here is an example of implementing a
 * fallback for SVG images which are not supported in VML-based browsers.
 *
 * (code)
 * var mxVmlCanvas2DImage = mxVmlCanvas2D.prototype.image;
 * mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
 * {
 *   if (src.substring(src.length - 4, src.length) == '.svg')
 *   {
 *     src = 'http://www.jgraph.com/images/mxgraph.gif';
 *   }
 *
 *   mxVmlCanvas2DImage.apply(this, arguments);
 * };
 * (end)
 *
 * To disable anti-aliasing in the output, use the following code.
 *
 * (code)
 * document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{antialias:false;)}';
 * (end)
 *
 * A description of the public API is available in <mxXmlCanvas2D>. Note that
 * there is a known issue in VML where gradients are painted using the outer
 * bounding box of rotated shapes, not the actual bounds of the shape. See
 * also <text> for plain text label restrictions in shapes for VML.
 */
var mxVmlCanvas2D = function (root) {
  mxAbstractCanvas2D.call(this);

  /**
   * Variable: root
   *
   * Reference to the container for the SVG content.
   */
  this.root = root;
};

/**
 * Extends mxAbstractCanvas2D
 */
mxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D);

/**
 * Variable: path
 *
 * Holds the current DOM node.
 */
mxVmlCanvas2D.prototype.node = null;

/**
 * Variable: textEnabled
 *
 * Specifies if text output should be enabledetB. Default is true.
 */
mxVmlCanvas2D.prototype.textEnabled = true;

/**
 * Variable: moveOp
 *
 * Contains the string used for moving in paths. Default is 'm'.
 */
mxVmlCanvas2D.prototype.moveOp = 'm';

/**
 * Variable: lineOp
 *
 * Contains the string used for moving in paths. Default is 'l'.
 */
mxVmlCanvas2D.prototype.lineOp = 'l';

/**
 * Variable: curveOp
 *
 * Contains the string used for bezier curves. Default is 'c'.
 */
mxVmlCanvas2D.prototype.curveOp = 'c';

/**
 * Variable: closeOp
 *
 * Holds the operator for closing curves. Default is 'x e'.
 */
mxVmlCanvas2D.prototype.closeOp = 'x';

/**
 * Variable: rotatedHtmlBackground
 *
 * Background color for rotated HTML. Default is ''. This can be set to eg.
 * white to improve rendering of rotated text in VML for IE9.
 */
mxVmlCanvas2D.prototype.rotatedHtmlBackground = '';

/**
 * Variable: vmlScale
 *
 * Specifies the scale used to draw VML shapes.
 */
mxVmlCanvas2D.prototype.vmlScale = 1;

/**
 * Function: createElement
 *
 * Creates the given element using the document.
 */
mxVmlCanvas2D.prototype.createElement = function (name) {
  return document.createElement(name);
};

/**
 * Function: createVmlElement
 *
 * Creates a new element using <createElement> and prefixes the given name with
 * <mxClient.VML_PREFIX>.
 */
mxVmlCanvas2D.prototype.createVmlElement = function (name) {
  return this.createElement(mxClient.VML_PREFIX + ':' + name);
};

/**
 * Function: addNode
 *
 * Adds the current node to the <root>.
 */
mxVmlCanvas2D.prototype.addNode = function (filled, stroked) {
  var node = this.node;
  var s = this.state;

  if (node != null) {
    if (node.nodeName == 'shape') {
      // Checks if the path is not empty
      if (this.path != null && this.path.length > 0) {
        node.path = this.path.join(' ') + ' e';
        node.style.width = this.root.style.width;
        node.style.height = this.root.style.height;
        node.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
      } else {
        return;
      }
    }

    node.strokeweight = this.format(Math.max(1, (s.strokeWidth * s.scale) / this.vmlScale)) + 'px';

    if (s.shadow) {
      this.root.appendChild(this.createShadow(node, filled && s.fillColor != null, stroked && s.strokeColor != null));
    }

    if (stroked && s.strokeColor != null) {
      node.stroked = 'true';
      node.strokecolor = s.strokeColor;
    } else {
      node.stroked = 'false';
    }

    node.appendChild(this.createStroke());

    if (filled && s.fillColor != null) {
      node.appendChild(this.createFill());
    } else if (this.pointerEvents && (node.nodeName != 'shape' || this.path[this.path.length - 1] == this.closeOp)) {
      node.appendChild(this.createTransparentFill());
    } else {
      node.filled = 'false';
    }

    // LATER: Update existing DOM for performance
    this.root.appendChild(node);
  }
};

/**
 * Function: createTransparentFill
 *
 * Creates a transparent fill.
 */
mxVmlCanvas2D.prototype.createTransparentFill = function () {
  var fill = this.createVmlElement('fill');
  fill.src = mxClient.imageBasePath + '/transparent.gif';
  fill.type = 'tile';

  return fill;
};

/**
 * Function: createFill
 *
 * Creates a fill for the current state.
 */
mxVmlCanvas2D.prototype.createFill = function () {
  var s = this.state;

  // Gradients in foregrounds not supported because special gradients
  // with bounds must be created for each element in graphics-canvases
  var fill = this.createVmlElement('fill');
  fill.color = s.fillColor;

  if (s.gradientColor != null) {
    fill.type = 'gradient';
    fill.method = 'none';
    fill.color2 = s.gradientColor;
    var angle = 180 - s.rotation;

    if (s.gradientDirection == mxConstants.DIRECTION_WEST) {
      angle -= 90 + (this.root.style.flip == 'x' ? 180 : 0);
    } else if (s.gradientDirection == mxConstants.DIRECTION_EAST) {
      angle += 90 + (this.root.style.flip == 'x' ? 180 : 0);
    } else if (s.gradientDirection == mxConstants.DIRECTION_NORTH) {
      angle -= 180 + (this.root.style.flip == 'y' ? -180 : 0);
    } else {
      angle += this.root.style.flip == 'y' ? -180 : 0;
    }

    if (this.root.style.flip == 'x' || this.root.style.flip == 'y') {
      angle *= -1;
    }

    // LATER: Fix outer bounding box for rotated shapes used in VML.
    fill.angle = mxUtils.mod(angle, 360);
    fill.opacity = s.alpha * s.gradientFillAlpha * 100 + '%';
    fill.setAttribute(mxClient.OFFICE_PREFIX + ':opacity2', s.alpha * s.gradientAlpha * 100 + '%');
  } else if (s.alpha < 1 || s.fillAlpha < 1) {
    fill.opacity = s.alpha * s.fillAlpha * 100 + '%';
  }

  return fill;
};
/**
 * Function: createStroke
 *
 * Creates a fill for the current state.
 */
mxVmlCanvas2D.prototype.createStroke = function () {
  var s = this.state;
  var stroke = this.createVmlElement('stroke');
  stroke.endcap = s.lineCap || 'flat';
  stroke.joinstyle = s.lineJoin || 'miter';
  stroke.miterlimit = s.miterLimit || '10';

  if (s.alpha < 1 || s.strokeAlpha < 1) {
    stroke.opacity = s.alpha * s.strokeAlpha * 100 + '%';
  }

  if (s.dashed) {
    stroke.dashstyle = this.getVmlDashStyle();
  }

  return stroke;
};

/**
 * Function: getVmlDashPattern
 *
 * Returns a VML dash pattern for the current dashPattern.
 * See http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx
 */
mxVmlCanvas2D.prototype.getVmlDashStyle = function () {
  var result = 'dash';

  if (typeof this.state.dashPattern === 'string') {
    var tok = this.state.dashPattern.split(' ');

    if (tok.length > 0 && tok[0] == 1) {
      result = '0 2';
    }
  }

  return result;
};

/**
 * Function: createShadow
 *
 * Creates a shadow for the given node.
 */
mxVmlCanvas2D.prototype.createShadow = function (node, filled, stroked) {
  var s = this.state;
  var rad = -s.rotation * (Math.PI / 180);
  var cos = Math.cos(rad);
  var sin = Math.sin(rad);

  var dx = s.shadowDx * s.scale;
  var dy = s.shadowDy * s.scale;

  if (this.root.style.flip == 'x') {
    dx *= -1;
  } else if (this.root.style.flip == 'y') {
    dy *= -1;
  }

  var shadow = node.cloneNode(true);
  shadow.style.marginLeft = Math.round(dx * cos - dy * sin) + 'px';
  shadow.style.marginTop = Math.round(dx * sin + dy * cos) + 'px';

  // Workaround for wrong cloning in IE8 standards mode
  if (document.documentMode == 8) {
    shadow.strokeweight = node.strokeweight;

    if (node.nodeName == 'shape') {
      shadow.path = this.path.join(' ') + ' e';
      shadow.style.width = this.root.style.width;
      shadow.style.height = this.root.style.height;
      shadow.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height);
    }
  }

  if (stroked) {
    shadow.strokecolor = s.shadowColor;
    shadow.appendChild(this.createShadowStroke());
  } else {
    shadow.stroked = 'false';
  }

  if (filled) {
    shadow.appendChild(this.createShadowFill());
  } else {
    shadow.filled = 'false';
  }

  return shadow;
};

/**
 * Function: createShadowFill
 *
 * Creates the fill for the shadow.
 */
mxVmlCanvas2D.prototype.createShadowFill = function () {
  var fill = this.createVmlElement('fill');
  fill.color = this.state.shadowColor;
  fill.opacity = this.state.alpha * this.state.shadowAlpha * 100 + '%';

  return fill;
};

/**
 * Function: createShadowStroke
 *
 * Creates the stroke for the shadow.
 */
mxVmlCanvas2D.prototype.createShadowStroke = function () {
  var stroke = this.createStroke();
  stroke.opacity = this.state.alpha * this.state.shadowAlpha * 100 + '%';

  return stroke;
};

/**
 * Function: rotate
 *
 * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
 */
mxVmlCanvas2D.prototype.rotate = function (theta, flipH, flipV, cx, cy) {
  if (flipH && flipV) {
    theta += 180;
  } else if (flipH) {
    this.root.style.flip = 'x';
  } else if (flipV) {
    this.root.style.flip = 'y';
  }

  if (flipH ? !flipV : flipV) {
    theta *= -1;
  }

  this.root.style.rotation = theta;
  this.state.rotation = this.state.rotation + theta;
  this.state.rotationCx = cx;
  this.state.rotationCy = cy;
};

/**
 * Function: begin
 *
 * Extends superclass to create path.
 */
mxVmlCanvas2D.prototype.begin = function () {
  mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
  this.node = this.createVmlElement('shape');
  this.node.style.position = 'absolute';
};

/**
 * Function: quadTo
 *
 * Replaces quadratic curve with bezier curve in VML.
 */
mxVmlCanvas2D.prototype.quadTo = function (x1, y1, x2, y2) {
  var s = this.state;

  var cpx0 = (this.lastX + s.dx) * s.scale;
  var cpy0 = (this.lastY + s.dy) * s.scale;
  var qpx1 = (x1 + s.dx) * s.scale;
  var qpy1 = (y1 + s.dy) * s.scale;
  var cpx3 = (x2 + s.dx) * s.scale;
  var cpy3 = (y2 + s.dy) * s.scale;

  var cpx1 = cpx0 + (2 / 3) * (qpx1 - cpx0);
  var cpy1 = cpy0 + (2 / 3) * (qpy1 - cpy0);

  var cpx2 = cpx3 + (2 / 3) * (qpx1 - cpx3);
  var cpy2 = cpy3 + (2 / 3) * (qpy1 - cpy3);

  this.path.push(
    'c ' +
      this.format(cpx1) +
      ' ' +
      this.format(cpy1) +
      ' ' +
      this.format(cpx2) +
      ' ' +
      this.format(cpy2) +
      ' ' +
      this.format(cpx3) +
      ' ' +
      this.format(cpy3)
  );
  this.lastX = cpx3 / s.scale - s.dx;
  this.lastY = cpy3 / s.scale - s.dy;
};

/**
 * Function: createRect
 *
 * Sets the glass gradient.
 */
mxVmlCanvas2D.prototype.createRect = function (nodeName, x, y, w, h) {
  var s = this.state;
  var n = this.createVmlElement(nodeName);
  n.style.position = 'absolute';
  n.style.left = this.format((x + s.dx) * s.scale) + 'px';
  n.style.top = this.format((y + s.dy) * s.scale) + 'px';
  n.style.width = this.format(w * s.scale) + 'px';
  n.style.height = this.format(h * s.scale) + 'px';

  return n;
};

/**
 * Function: rect
 *
 * Sets the current path to a rectangle.
 */
mxVmlCanvas2D.prototype.rect = function (x, y, w, h) {
  this.node = this.createRect('rect', x, y, w, h);
};

/**
 * Function: roundrect
 *
 * Sets the current path to a rounded rectangle.
 */
mxVmlCanvas2D.prototype.roundrect = function (x, y, w, h, dx, dy) {
  this.node = this.createRect('roundrect', x, y, w, h);
  // SetAttribute needed here for IE8
  this.node.setAttribute('arcsize', Math.max((dx * 100) / w, (dy * 100) / h) + '%');
};

/**
 * Function: ellipse
 *
 * Sets the current path to an ellipse.
 */
mxVmlCanvas2D.prototype.ellipse = function (x, y, w, h) {
  this.node = this.createRect('oval', x, y, w, h);
};

/**
 * Function: image
 *
 * Paints an image.
 */
mxVmlCanvas2D.prototype.image = function (x, y, w, h, src, aspect, flipH, flipV) {
  var node = null;

  if (!aspect) {
    node = this.createRect('image', x, y, w, h);
    node.src = src;
  } else {
    // Uses fill with aspect to avoid asynchronous update of size
    node = this.createRect('rect', x, y, w, h);
    node.stroked = 'false';

    // Handles image aspect via fill
    var fill = this.createVmlElement('fill');
    fill.aspect = aspect ? 'atmost' : 'ignore';
    fill.rotate = 'true';
    fill.type = 'frame';
    fill.src = src;

    node.appendChild(fill);
  }

  if (flipH && flipV) {
    node.style.rotation = '180';
  } else if (flipH) {
    node.style.flip = 'x';
  } else if (flipV) {
    node.style.flip = 'y';
  }

  if (this.state.alpha < 1 || this.state.fillAlpha < 1) {
    // KNOWN: Borders around transparent images in IE<9. Using fill.opacity
    // fixes this problem by adding a white background in all IE versions.
    node.style.filter += 'alpha(opacity=' + this.state.alpha * this.state.fillAlpha * 100 + ')';
  }

  this.root.appendChild(node);
};

/**
 * Function: createText
 *
 * Creates the innermost element that contains the HTML text.
 */
mxVmlCanvas2D.prototype.createDiv = function (str, align, valign, overflow) {
  var div = this.createElement('div');
  var state = this.state;

  var css = '';

  if (state.fontBackgroundColor != null) {
    css += 'background-color:' + mxUtils.htmlEntities(state.fontBackgroundColor) + ';';
  }

  if (state.fontBorderColor != null) {
    css += 'border:1px solid ' + mxUtils.htmlEntities(state.fontBorderColor) + ';';
  }

  if (mxUtils.isNode(str)) {
    div.appendChild(str);
  } else {
    if (overflow != 'fill' && overflow != 'width') {
      var div2 = this.createElement('div');
      div2.style.cssText = css;
      div2.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block';
      div2.style.zoom = '1';
      div2.style.textDecoration = 'inherit';
      div2.innerHTML = str;
      div.appendChild(div2);
    } else {
      div.style.cssText = css;
      div.innerHTML = str;
    }
  }

  var style = div.style;

  style.fontSize = state.fontSize / this.vmlScale + 'px';
  style.fontFamily = state.fontFamily;
  style.color = state.fontColor;
  style.verticalAlign = 'top';
  style.textAlign = align || 'left';
  style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT
    ? (state.fontSize * mxConstants.LINE_HEIGHT) / this.vmlScale + 'px'
    : mxConstants.LINE_HEIGHT;

  if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
    style.fontWeight = 'bold';
  }

  if ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
    style.fontStyle = 'italic';
  }

  if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
    style.textDecoration = 'underline';
  }

  return div;
};

/**
 * Function: text
 *
 * Paints the given text. Possible values for format are empty string for plain
 * text and html for HTML markup. Clipping, text background and border are not
 * supported for plain text in VML.
 */
mxVmlCanvas2D.prototype.text = function (x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) {
  if (this.textEnabled && str != null) {
    var s = this.state;

    if (format == 'html') {
      if (s.rotation != null) {
        var pt = this.rotatePoint(x, y, s.rotation, s.rotationCx, s.rotationCy);

        x = pt.x;
        y = pt.y;
      }

      if (document.documentMode == 8 && !mxClient.IS_EM) {
        x += s.dx;
        y += s.dy;

        // Workaround for rendering offsets
        if (overflow != 'fill' && valign == mxConstants.ALIGN_TOP) {
          y -= 1;
        }
      } else {
        x *= s.scale;
        y *= s.scale;
      }

      // Adds event transparency in IE8 standards without the transparent background
      // filter which cannot be used due to bugs in the zoomed bounding box (too slow)
      // FIXME: No event transparency if inside v:rect (ie part of shape)
      // KNOWN: Offset wrong for rotated text with word that are longer than the wrapping
      // width in IE8 because real width of text cannot be determined here.
      // This should be fixed in mxText.updateBoundingBox by calling before this and
      // passing the real width to this method if not clipped and wrapped.
      var abs =
        document.documentMode == 8 && !mxClient.IS_EM ? this.createVmlElement('group') : this.createElement('div');
      abs.style.position = 'absolute';
      abs.style.display = 'inline';
      abs.style.left = this.format(x) + 'px';
      abs.style.top = this.format(y) + 'px';
      abs.style.zoom = s.scale;

      var box = this.createElement('div');
      box.style.position = 'relative';
      box.style.display = 'inline';

      var margin = mxUtils.getAlignmentAsPoint(align, valign);
      var dx = margin.x;
      var dy = margin.y;

      var div = this.createDiv(str, align, valign, overflow);
      var inner = this.createElement('div');

      if (dir != null) {
        div.setAttribute('dir', dir);
      }

      if (wrap && w > 0) {
        if (!clip) {
          div.style.width = Math.round(w) + 'px';
        }

        div.style.wordWrap = mxConstants.WORD_WRAP;
        div.style.whiteSpace = 'normal';

        // LATER: Check if other cases need to be handled
        if (div.style.wordWrap == 'break-word') {
          var tmp = div;

          if (tmp.firstChild != null && tmp.firstChild.nodeName == 'DIV') {
            tmp.firstChild.style.width = '100%';
          }
        }
      } else {
        div.style.whiteSpace = 'nowrap';
      }

      var rot = s.rotation + (rotation || 0);

      if (this.rotateHtml && rot != 0) {
        inner.style.display = 'inline';
        inner.style.zoom = '1';
        inner.appendChild(div);

        // Box not needed for rendering in IE8 standards
        if (document.documentMode == 8 && !mxClient.IS_EM && this.root.nodeName != 'DIV') {
          box.appendChild(inner);
          abs.appendChild(box);
        } else {
          abs.appendChild(inner);
        }
      } else if (document.documentMode == 8 && !mxClient.IS_EM) {
        box.appendChild(div);
        abs.appendChild(box);
      } else {
        div.style.display = 'inline';
        abs.appendChild(div);
      }

      // Inserts the node into the DOM
      if (this.root.nodeName != 'DIV') {
        // Rectangle to fix position in group
        var rect = this.createVmlElement('rect');
        rect.stroked = 'false';
        rect.filled = 'false';

        rect.appendChild(abs);
        this.root.appendChild(rect);
      } else {
        this.root.appendChild(abs);
      }

      if (clip) {
        div.style.overflow = 'hidden';
        div.style.width = Math.round(w) + 'px';

        if (!mxClient.IS_QUIRKS) {
          div.style.maxHeight = Math.round(h) + 'px';
        }
      } else if (overflow == 'fill') {
        // KNOWN: Affects horizontal alignment in quirks
        // but fill should only be used with align=left
        div.style.overflow = 'hidden';
        div.style.width = Math.max(0, w) + 1 + 'px';
        div.style.height = Math.max(0, h) + 1 + 'px';
      } else if (overflow == 'width') {
        // KNOWN: Affects horizontal alignment in quirks
        // but fill should only be used with align=left
        div.style.overflow = 'hidden';
        div.style.width = Math.max(0, w) + 1 + 'px';
        div.style.maxHeight = Math.max(0, h) + 1 + 'px';
      }

      if (this.rotateHtml && rot != 0) {
        var rad = rot * (Math.PI / 180);

        // Precalculate cos and sin for the rotation
        var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
        var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));

        rad %= 2 * Math.PI;
        if (rad < 0) rad += 2 * Math.PI;
        rad %= Math.PI;
        if (rad > Math.PI / 2) rad = Math.PI - rad;

        var cos = Math.cos(rad);
        var sin = Math.sin(rad);

        // Adds div to document to measure size
        if (document.documentMode == 8 && !mxClient.IS_EM) {
          div.style.display = 'inline-block';
          inner.style.display = 'inline-block';
          box.style.display = 'inline-block';
        }

        div.style.visibility = 'hidden';
        div.style.position = 'absolute';
        document.body.appendChild(div);

        var sizeDiv = div;

        if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') {
          sizeDiv = sizeDiv.firstChild;
        }

        var tmp = sizeDiv.offsetWidth + 3;
        var oh = sizeDiv.offsetHeight;

        if (clip) {
          w = Math.min(w, tmp);
          oh = Math.min(oh, h);
        } else {
          w = tmp;
        }

        // Handles words that are longer than the given wrapping width
        if (wrap) {
          div.style.width = w + 'px';
        }

        // Simulates max-height in quirks
        if (mxClient.IS_QUIRKS && (clip || overflow == 'width') && oh > h) {
          oh = h;

          // Quirks does not support maxHeight
          div.style.height = oh + 'px';
        }

        h = oh;

        var top_fix = (h - h * cos + w * -sin) / 2 - real_sin * w * (dx + 0.5) + real_cos * h * (dy + 0.5);
        var left_fix = (w - w * cos + h * -sin) / 2 + real_cos * w * (dx + 0.5) + real_sin * h * (dy + 0.5);

        if (abs.nodeName == 'group' && this.root.nodeName == 'DIV') {
          // Workaround for bug where group gets moved away if left and top are non-zero in IE8 standards
          var pos = this.createElement('div');
          pos.style.display = 'inline-block';
          pos.style.position = 'absolute';
          pos.style.left = this.format(x + (left_fix - w / 2) * s.scale) + 'px';
          pos.style.top = this.format(y + (top_fix - h / 2) * s.scale) + 'px';

          abs.parentNode.appendChild(pos);
          pos.appendChild(abs);
        } else {
          var sc = document.documentMode == 8 && !mxClient.IS_EM ? 1 : s.scale;

          abs.style.left = this.format(x + (left_fix - w / 2) * sc) + 'px';
          abs.style.top = this.format(y + (top_fix - h / 2) * sc) + 'px';
        }

        // KNOWN: Rotated text rendering quality is bad for IE9 quirks
        inner.style.filter =
          'progid:DXImageTransform.Microsoft.Matrix(M11=' +
          real_cos +
          ', M12=' +
          real_sin +
          ', M21=' +
          -real_sin +
          ', M22=' +
          real_cos +
          ", sizingMethod='auto expand')";
        inner.style.backgroundColor = this.rotatedHtmlBackground;

        if (this.state.alpha < 1) {
          inner.style.filter += 'alpha(opacity=' + this.state.alpha * 100 + ')';
        }

        // Restore parent node for DIV
        inner.appendChild(div);
        div.style.position = '';
        div.style.visibility = '';
      } else if (document.documentMode != 8 || mxClient.IS_EM) {
        div.style.verticalAlign = 'top';

        if (this.state.alpha < 1) {
          abs.style.filter = 'alpha(opacity=' + this.state.alpha * 100 + ')';
        }

        // Adds div to document to measure size
        var divParent = div.parentNode;
        div.style.visibility = 'hidden';
        document.body.appendChild(div);

        w = div.offsetWidth;
        var oh = div.offsetHeight;

        // Simulates max-height in quirks
        if (mxClient.IS_QUIRKS && clip && oh > h) {
          oh = h;

          // Quirks does not support maxHeight
          div.style.height = oh + 'px';
        }

        h = oh;

        div.style.visibility = '';
        divParent.appendChild(div);

        abs.style.left = this.format(x + w * dx * this.state.scale) + 'px';
        abs.style.top = this.format(y + h * dy * this.state.scale) + 'px';
      } else {
        if (this.state.alpha < 1) {
          div.style.filter = 'alpha(opacity=' + this.state.alpha * 100 + ')';
        }

        // Faster rendering in IE8 without offsetWidth/Height
        box.style.left = dx * 100 + '%';
        box.style.top = dy * 100 + '%';
      }
    } else {
      this.plainText(
        x,
        y,
        w,
        h,
        mxUtils.htmlEntities(str, false),
        align,
        valign,
        wrap,
        format,
        overflow,
        clip,
        rotation,
        dir
      );
    }
  }
};

/**
 * Function: plainText
 *
 * Paints the outline of the current path.
 */
mxVmlCanvas2D.prototype.plainText = function (
  x,
  y,
  w,
  h,
  str,
  align,
  valign,
  wrap,
  format,
  overflow,
  clip,
  rotation,
  dir
) {
  // TextDirection is ignored since this code is not used (format is always HTML in the text function)
  var s = this.state;
  x = (x + s.dx) * s.scale;
  y = (y + s.dy) * s.scale;

  var node = this.createVmlElement('shape');
  node.style.width = '1px';
  node.style.height = '1px';
  node.stroked = 'false';

  var fill = this.createVmlElement('fill');
  fill.color = s.fontColor;
  fill.opacity = s.alpha * 100 + '%';
  node.appendChild(fill);

  var path = this.createVmlElement('path');
  path.textpathok = 'true';
  path.v = 'm ' + this.format(0) + ' ' + this.format(0) + ' l ' + this.format(1) + ' ' + this.format(0);

  node.appendChild(path);

  // KNOWN: Font family and text decoration ignored
  var tp = this.createVmlElement('textpath');
  tp.style.cssText = 'v-text-align:' + align;
  tp.style.align = align;
  tp.style.fontFamily = s.fontFamily;
  tp.string = str;
  tp.on = 'true';

  // Scale via fontsize instead of node.style.zoom for correct offsets in IE8
  var size = (s.fontSize * s.scale) / this.vmlScale;
  tp.style.fontSize = size + 'px';

  // Bold
  if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
    tp.style.fontWeight = 'bold';
  }

  // Italic
  if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
    tp.style.fontStyle = 'italic';
  }

  // Underline
  if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
    tp.style.textDecoration = 'underline';
  }

  var lines = str.split('\n');
  var textHeight = size + (lines.length - 1) * size * mxConstants.LINE_HEIGHT;
  var dx = 0;
  var dy = 0;

  if (valign == mxConstants.ALIGN_BOTTOM) {
    dy = -textHeight / 2;
  } else if (valign != mxConstants.ALIGN_MIDDLE) {
    // top
    dy = textHeight / 2;
  }

  if (rotation != null) {
    node.style.rotation = rotation;
    var rad = rotation * (Math.PI / 180);
    dx = Math.sin(rad) * dy;
    dy = Math.cos(rad) * dy;
  }

  // FIXME: Clipping is relative to bounding box
  /*if (clip)
	{
		node.style.clip = 'rect(0px ' + this.format(w) + 'px ' + this.format(h) + 'px 0px)';
	}*/

  node.appendChild(tp);
  node.style.left = this.format(x - dx) + 'px';
  node.style.top = this.format(y + dy) + 'px';

  this.root.appendChild(node);
};

/**
 * Function: stroke
 *
 * Paints the outline of the current path.
 */
mxVmlCanvas2D.prototype.stroke = function () {
  this.addNode(false, true);
};

/**
 * Function: fill
 *
 * Fills the current path.
 */
mxVmlCanvas2D.prototype.fill = function () {
  this.addNode(true, false);
};

/**
 * Function: fillAndStroke
 *
 * Fills and paints the outline of the current path.
 */
mxVmlCanvas2D.prototype.fillAndStroke = function () {
  this.addNode(true, true);
};

/**
 * util/mxGuide.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGuide
 *
 * Implements the alignment of selection cells to other cells in the graph.
 *
 * Constructor: mxGuide
 *
 * Constructs a new guide object.
 */
function mxGuide(graph, states) {
  this.graph = graph;
  this.setStates(states);
}

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph> instance.
 */
mxGuide.prototype.graph = null;

/**
 * Variable: states
 *
 * Contains the <mxCellStates> that are used for alignment.
 */
mxGuide.prototype.states = null;

/**
 * Variable: horizontal
 *
 * Specifies if horizontal guides are enabled. Default is true.
 */
mxGuide.prototype.horizontal = true;

/**
 * Variable: vertical
 *
 * Specifies if vertical guides are enabled. Default is true.
 */
mxGuide.prototype.vertical = true;

/**
 * Variable: guideX
 *
 * Holds the <mxShape> for the horizontal guide.
 */
mxGuide.prototype.guideX = null;

/**
 * Variable: guideY
 *
 * Holds the <mxShape> for the vertical guide.
 */
mxGuide.prototype.guideY = null;

/**
 * Variable: rounded
 *
 * Specifies if rounded coordinates should be used. Default is false.
 */
mxGuide.prototype.rounded = false;

/**
 * Variable: tolerance
 *
 * Default tolerance in px if grid is disabled. Default is 2.
 */
mxGuide.prototype.tolerance = 2;

/**
 * Function: setStates
 *
 * Sets the <mxCellStates> that should be used for alignment.
 */
mxGuide.prototype.setStates = function (states) {
  this.states = states;
};

/**
 * Function: isEnabledForEvent
 *
 * Returns true if the guide should be enabled for the given native event. This
 * implementation always returns true.
 */
mxGuide.prototype.isEnabledForEvent = function (evt) {
  return true;
};

/**
 * Function: getGuideTolerance
 *
 * Returns the tolerance for the guides. Default value is gridSize / 2.
 */
mxGuide.prototype.getGuideTolerance = function (gridEnabled) {
  return gridEnabled && this.graph.gridEnabled ? this.graph.gridSize / 2 : this.tolerance;
};

/**
 * Function: createGuideShape
 *
 * Returns the mxShape to be used for painting the respective guide. This
 * implementation returns a new, dashed and crisp <mxPolyline> using
 * <mxConstants.GUIDE_COLOR> and <mxConstants.GUIDE_STROKEWIDTH> as the format.
 *
 * Parameters:
 *
 * horizontal - Boolean that specifies which guide should be created.
 */
mxGuide.prototype.createGuideShape = function (horizontal) {
  var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);
  guide.isDashed = true;

  return guide;
};

/**
 * Function: isStateIgnored
 *
 * Returns true if the given state should be ignored.
 */
mxGuide.prototype.isStateIgnored = function (state) {
  return false;
};

/**
 * Function: move
 *
 * Moves the <bounds> by the given <mxPoint> and returnt the snapped point.
 */
mxGuide.prototype.move = function (bounds, delta, gridEnabled, clone) {
  if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null) {
    var scale = this.graph.getView().scale;
    var tt = this.getGuideTolerance(gridEnabled) * scale;
    var b = bounds.clone();
    b.x += delta.x;
    b.y += delta.y;
    var overrideX = false;
    var stateX = null;
    var valueX = null;
    var overrideY = false;
    var stateY = null;
    var valueY = null;
    var ttX = tt;
    var ttY = tt;
    var left = b.x;
    var right = b.x + b.width;
    var center = b.getCenterX();
    var top = b.y;
    var bottom = b.y + b.height;
    var middle = b.getCenterY();

    // Snaps the left, center and right to the given x-coordinate
    function snapX(x, state, centerAlign) {
      var override = false;

      if (centerAlign && Math.abs(x - center) < ttX) {
        delta.x = x - bounds.getCenterX();
        ttX = Math.abs(x - center);
        override = true;
      } else if (!centerAlign) {
        if (Math.abs(x - left) < ttX) {
          delta.x = x - bounds.x;
          ttX = Math.abs(x - left);
          override = true;
        } else if (Math.abs(x - right) < ttX) {
          delta.x = x - bounds.x - bounds.width;
          ttX = Math.abs(x - right);
          override = true;
        }
      }

      if (override) {
        stateX = state;
        valueX = x;

        if (this.guideX == null) {
          this.guideX = this.createGuideShape(true);

          // Makes sure to use either VML or SVG shapes in order to implement
          // event-transparency on the background area of the rectangle since
          // HTML shapes do not let mouseevents through even when transparent
          this.guideX.dialect =
            this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
          this.guideX.pointerEvents = false;
          this.guideX.init(this.graph.getView().getOverlayPane());
        }
      }

      overrideX = overrideX || override;
    }

    // Snaps the top, middle or bottom to the given y-coordinate
    function snapY(y, state, centerAlign) {
      var override = false;

      if (centerAlign && Math.abs(y - middle) < ttY) {
        delta.y = y - bounds.getCenterY();
        ttY = Math.abs(y - middle);
        override = true;
      } else if (!centerAlign) {
        if (Math.abs(y - top) < ttY) {
          delta.y = y - bounds.y;
          ttY = Math.abs(y - top);
          override = true;
        } else if (Math.abs(y - bottom) < ttY) {
          delta.y = y - bounds.y - bounds.height;
          ttY = Math.abs(y - bottom);
          override = true;
        }
      }

      if (override) {
        stateY = state;
        valueY = y;

        if (this.guideY == null) {
          this.guideY = this.createGuideShape(false);

          // Makes sure to use either VML or SVG shapes in order to implement
          // event-transparency on the background area of the rectangle since
          // HTML shapes do not let mouseevents through even when transparent
          this.guideY.dialect =
            this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
          this.guideY.pointerEvents = false;
          this.guideY.init(this.graph.getView().getOverlayPane());
        }
      }

      overrideY = overrideY || override;
    }

    for (var i = 0; i < this.states.length; i++) {
      var state = this.states[i];

      if (state != null && !this.isStateIgnored(state)) {
        // Align x
        if (this.horizontal) {
          snapX.call(this, state.getCenterX(), state, true);
          snapX.call(this, state.x, state, false);
          snapX.call(this, state.x + state.width, state, false);

          // Aligns left and right of shape to center of page
          if (state.cell == null) {
            snapX.call(this, state.getCenterX(), state, false);
          }
        }

        // Align y
        if (this.vertical) {
          snapY.call(this, state.getCenterY(), state, true);
          snapY.call(this, state.y, state, false);
          snapY.call(this, state.y + state.height, state, false);

          // Aligns left and right of shape to center of page
          if (state.cell == null) {
            snapY.call(this, state.getCenterY(), state, false);
          }
        }
      }
    }

    // Moves cells to the raster if not aligned
    this.graph.snapDelta(delta, bounds, !gridEnabled, overrideX, overrideY);
    delta = this.getDelta(bounds, stateX, delta.x, stateY, delta.y);

    // Redraws the guides
    var c = this.graph.container;

    if (!overrideX && this.guideX != null) {
      this.guideX.node.style.visibility = 'hidden';
    } else if (this.guideX != null) {
      var minY = null;
      var maxY = null;

      if (stateX != null && bounds != null) {
        minY = Math.min(bounds.y + delta.y - this.graph.panDy, stateX.y);
        maxY = Math.max(bounds.y + bounds.height + delta.y - this.graph.panDy, stateX.y + stateX.height);
      }

      if (minY != null && maxY != null) {
        this.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)];
      } else {
        this.guideX.points = [
          new mxPoint(valueX, -this.graph.panDy),
          new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy),
        ];
      }

      this.guideX.stroke = this.getGuideColor(stateX, true);
      this.guideX.node.style.visibility = 'visible';
      this.guideX.redraw();
    }

    if (!overrideY && this.guideY != null) {
      this.guideY.node.style.visibility = 'hidden';
    } else if (this.guideY != null) {
      var minX = null;
      var maxX = null;

      if (stateY != null && bounds != null) {
        minX = Math.min(bounds.x + delta.x - this.graph.panDx, stateY.x);
        maxX = Math.max(bounds.x + bounds.width + delta.x - this.graph.panDx, stateY.x + stateY.width);
      }

      if (minX != null && maxX != null) {
        this.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)];
      } else {
        this.guideY.points = [
          new mxPoint(-this.graph.panDx, valueY),
          new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY),
        ];
      }

      this.guideY.stroke = this.getGuideColor(stateY, false);
      this.guideY.node.style.visibility = 'visible';
      this.guideY.redraw();
    }
  }

  return delta;
};

/**
 * Function: getDelta
 *
 * Rounds to pixels for virtual states (eg. page guides)
 */
mxGuide.prototype.getDelta = function (bounds, stateX, dx, stateY, dy) {
  var s = this.graph.view.scale;

  if (this.rounded || (stateX != null && stateX.cell == null)) {
    dx = Math.round((bounds.x + dx) / s) * s - bounds.x;
  }

  if (this.rounded || (stateY != null && stateY.cell == null)) {
    dy = Math.round((bounds.y + dy) / s) * s - bounds.y;
  }

  return new mxPoint(dx, dy);
};

/**
 * Function: getGuideColor
 *
 * Returns the color for the given state.
 */
mxGuide.prototype.getGuideColor = function (state, horizontal) {
  return mxConstants.GUIDE_COLOR;
};

/**
 * Function: hide
 *
 * Hides all current guides.
 */
mxGuide.prototype.hide = function () {
  this.setVisible(false);
};

/**
 * Function: setVisible
 *
 * Shows or hides the current guides.
 */
mxGuide.prototype.setVisible = function (visible) {
  if (this.guideX != null) {
    this.guideX.node.style.visibility = visible ? 'visible' : 'hidden';
  }

  if (this.guideY != null) {
    this.guideY.node.style.visibility = visible ? 'visible' : 'hidden';
  }
};

/**
 * Function: destroy
 *
 * Destroys all resources that this object uses.
 */
mxGuide.prototype.destroy = function () {
  if (this.guideX != null) {
    this.guideX.destroy();
    this.guideX = null;
  }

  if (this.guideY != null) {
    this.guideY.destroy();
    this.guideY = null;
  }
};

/**
 * shape/mxShape.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxShape
 *
 * Base class for all shapes. A shape in mxGraph is a
 * separate implementation for SVG, VML and HTML. Which
 * implementation to use is controlled by the <dialect>
 * property which is assigned from within the <mxCellRenderer>
 * when the shape is created. The dialect must be assigned
 * for a shape, and it does normally depend on the browser and
 * the confiuration of the graph (see <mxGraph> rendering hint).
 *
 * For each supported shape in SVG and VML, a corresponding
 * shape exists in mxGraph, namely for text, image, rectangle,
 * rhombus, ellipse and polyline. The other shapes are a
 * combination of these shapes (eg. label and swimlane)
 * or they consist of one or more (filled) path objects
 * (eg. actor and cylinder). The HTML implementation is
 * optional but may be required for a HTML-only view of
 * the graph.
 *
 * Custom Shapes:
 *
 * To extend from this class, the basic code looks as follows.
 * In the special case where the custom shape consists only of
 * one filled region or one filled region and an additional stroke
 * the <mxActor> and <mxCylinder> should be subclassed,
 * respectively.
 *
 * (code)
 * function CustomShape() { }
 *
 * CustomShape.prototype = new mxShape();
 * CustomShape.prototype.constructor = CustomShape;
 * (end)
 *
 * To register a custom shape in an existing graph instance,
 * one must register the shape under a new name in the graph's
 * cell renderer as follows:
 *
 * (code)
 * mxCellRenderer.registerShape('customShape', CustomShape);
 * (end)
 *
 * The second argument is the name of the constructor.
 *
 * In order to use the shape you can refer to the given name above
 * in a stylesheet. For example, to change the shape for the default
 * vertex style, the following code is used:
 *
 * (code)
 * var style = graph.getStylesheet().getDefaultVertexStyle();
 * style[mxConstants.STYLE_SHAPE] = 'customShape';
 * (end)
 *
 * Constructor: mxShape
 *
 * Constructs a new shape.
 */
function mxShape(stencil) {
  this.stencil = stencil;
  this.initStyles();
}

/**
 * Variable: dialect
 *
 * Holds the dialect in which the shape is to be painted.
 * This can be one of the DIALECT constants in <mxConstants>.
 */
mxShape.prototype.dialect = null;

/**
 * Variable: scale
 *
 * Holds the scale in which the shape is being painted.
 */
mxShape.prototype.scale = 1;

/**
 * Variable: antiAlias
 *
 * Rendering hint for configuring the canvas.
 */
mxShape.prototype.antiAlias = true;

/**
 * Variable: minSvgStrokeWidth
 *
 * Minimum stroke width for SVG output.
 */
mxShape.prototype.minSvgStrokeWidth = 1;

/**
 * Variable: bounds
 *
 * Holds the <mxRectangle> that specifies the bounds of this shape.
 */
mxShape.prototype.bounds = null;

/**
 * Variable: points
 *
 * Holds the array of <mxPoints> that specify the points of this shape.
 */
mxShape.prototype.points = null;

/**
 * Variable: node
 *
 * Holds the outermost DOM node that represents this shape.
 */
mxShape.prototype.node = null;

/**
 * Variable: state
 *
 * Optional reference to the corresponding <mxCellState>.
 */
mxShape.prototype.state = null;

/**
 * Variable: style
 *
 * Optional reference to the style of the corresponding <mxCellState>.
 */
mxShape.prototype.style = null;

/**
 * Variable: boundingBox
 *
 * Contains the bounding box of the shape, that is, the smallest rectangle
 * that includes all pixels of the shape.
 */
mxShape.prototype.boundingBox = null;

/**
 * Variable: stencil
 *
 * Holds the <mxStencil> that defines the shape.
 */
mxShape.prototype.stencil = null;

/**
 * Variable: svgStrokeTolerance
 *
 * Event-tolerance for SVG strokes (in px). Default is 8. This is only passed
 * to the canvas in <createSvgCanvas> if <pointerEvents> is true.
 */
mxShape.prototype.svgStrokeTolerance = 8;

/**
 * Variable: pointerEvents
 *
 * Specifies if pointer events should be handled. Default is true.
 */
mxShape.prototype.pointerEvents = true;

/**
 * Variable: svgPointerEvents
 *
 * Specifies if pointer events should be handled. Default is true.
 */
mxShape.prototype.svgPointerEvents = 'all';

/**
 * Variable: shapePointerEvents
 *
 * Specifies if pointer events outside of shape should be handled. Default
 * is false.
 */
mxShape.prototype.shapePointerEvents = false;

/**
 * Variable: stencilPointerEvents
 *
 * Specifies if pointer events outside of stencils should be handled. Default
 * is false. Set this to true for backwards compatibility with the 1.x branch.
 */
mxShape.prototype.stencilPointerEvents = false;

/**
 * Variable: vmlScale
 *
 * Scale for improving the precision of VML rendering. Default is 1.
 */
mxShape.prototype.vmlScale = 1;

/**
 * Variable: outline
 *
 * Specifies if the shape should be drawn as an outline. This disables all
 * fill colors and can be used to disable other drawing states that should
 * not be painted for outlines. Default is false. This should be set before
 * calling <apply>.
 */
mxShape.prototype.outline = false;

/**
 * Variable: visible
 *
 * Specifies if the shape is visible. Default is true.
 */
mxShape.prototype.visible = true;

/**
 * Variable: useSvgBoundingBox
 *
 * Allows to use the SVG bounding box in SVG. Default is false for performance
 * reasons.
 */
mxShape.prototype.useSvgBoundingBox = false;

/**
 * Function: init
 *
 * Initializes the shape by creaing the DOM node using <create>
 * and adding it into the given container.
 *
 * Parameters:
 *
 * container - DOM node that will contain the shape.
 */
mxShape.prototype.init = function (container) {
  if (this.node == null) {
    this.node = this.create(container);

    if (container != null) {
      container.appendChild(this.node);
    }
  }
};

/**
 * Function: initStyles
 *
 * Sets the styles to their default values.
 */
mxShape.prototype.initStyles = function (container) {
  this.strokewidth = 1;
  this.rotation = 0;
  this.opacity = 100;
  this.fillOpacity = 100;
  this.strokeOpacity = 100;
  this.flipH = false;
  this.flipV = false;
};

/**
 * Function: isParseVml
 *
 * Specifies if any VML should be added via insertAdjacentHtml to the DOM. This
 * is only needed in IE8 and only if the shape contains VML markup. This method
 * returns true.
 */
mxShape.prototype.isParseVml = function () {
  return true;
};

/**
 * Function: isHtmlAllowed
 *
 * Returns true if HTML is allowed for this shape. This implementation always
 * returns false.
 */
mxShape.prototype.isHtmlAllowed = function () {
  return false;
};

/**
 * Function: getSvgScreenOffset
 *
 * Returns 0, or 0.5 if <strokewidth> % 2 == 1.
 */
mxShape.prototype.getSvgScreenOffset = function () {
  var sw = this.stencil && this.stencil.strokewidth != 'inherit' ? Number(this.stencil.strokewidth) : this.strokewidth;

  return mxUtils.mod(Math.max(1, Math.round(sw * this.scale)), 2) == 1 ? 0.5 : 0;
};

/**
 * Function: create
 *
 * Creates and returns the DOM node(s) for the shape in
 * the given container. This implementation invokes
 * <createSvg>, <createHtml> or <createVml> depending
 * on the <dialect> and style settings.
 *
 * Parameters:
 *
 * container - DOM node that will contain the shape.
 */
mxShape.prototype.create = function (container) {
  var node = null;

  if (container != null && container.ownerSVGElement != null) {
    node = this.createSvg(container);
  } else if (
    document.documentMode == 8 ||
    !mxClient.IS_VML ||
    (this.dialect != mxConstants.DIALECT_VML && this.isHtmlAllowed())
  ) {
    node = this.createHtml(container);
  } else {
    node = this.createVml(container);
  }

  return node;
};

/**
 * Function: createSvg
 *
 * Creates and returns the SVG node(s) to represent this shape.
 */
mxShape.prototype.createSvg = function () {
  return document.createElementNS(mxConstants.NS_SVG, 'g');
};

/**
 * Function: createVml
 *
 * Creates and returns the VML node to represent this shape.
 */
mxShape.prototype.createVml = function () {
  var node = document.createElement(mxClient.VML_PREFIX + ':group');
  node.style.position = 'absolute';

  return node;
};

/**
 * Function: createHtml
 *
 * Creates and returns the HTML DOM node(s) to represent
 * this shape. This implementation falls back to <createVml>
 * so that the HTML creation is optional.
 */
mxShape.prototype.createHtml = function () {
  var node = document.createElement('div');
  node.style.position = 'absolute';

  return node;
};

/**
 * Function: reconfigure
 *
 * Reconfigures this shape. This will update the colors etc in
 * addition to the bounds or points.
 */
mxShape.prototype.reconfigure = function () {
  this.redraw();
};

/**
 * Function: redraw
 *
 * Creates and returns the SVG node(s) to represent this shape.
 */
mxShape.prototype.redraw = function () {
  this.updateBoundsFromPoints();

  if (this.visible && this.checkBounds()) {
    this.node.style.visibility = 'visible';
    this.clear();

    if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML)) {
      this.redrawHtmlShape();
    } else {
      this.redrawShape();
    }

    this.updateBoundingBox();
  } else {
    this.node.style.visibility = 'hidden';
    this.boundingBox = null;
  }
};

/**
 * Function: clear
 *
 * Removes all child nodes and resets all CSS.
 */
mxShape.prototype.clear = function () {
  if (this.node.ownerSVGElement != null) {
    while (this.node.lastChild != null) {
      this.node.removeChild(this.node.lastChild);
    }
  } else {
    this.node.style.cssText = 'position:absolute;' + (this.cursor != null ? 'cursor:' + this.cursor + ';' : '');
    this.node.innerHTML = '';
  }
};

/**
 * Function: updateBoundsFromPoints
 *
 * Updates the bounds based on the points.
 */
mxShape.prototype.updateBoundsFromPoints = function () {
  var pts = this.points;

  if (pts != null && pts.length > 0 && pts[0] != null) {
    this.bounds = new mxRectangle(Number(pts[0].x), Number(pts[0].y), 1, 1);

    for (var i = 1; i < this.points.length; i++) {
      if (pts[i] != null) {
        this.bounds.add(new mxRectangle(Number(pts[i].x), Number(pts[i].y), 1, 1));
      }
    }
  }
};

/**
 * Function: getLabelBounds
 *
 * Returns the <mxRectangle> for the label bounds of this shape, based on the
 * given scaled and translated bounds of the shape. This method should not
 * change the rectangle in-place. This implementation returns the given rect.
 */
mxShape.prototype.getLabelBounds = function (rect) {
  var d = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
  var bounds = rect;

  // Normalizes argument for getLabelMargins hook
  if (
    d != mxConstants.DIRECTION_SOUTH &&
    d != mxConstants.DIRECTION_NORTH &&
    this.state != null &&
    this.state.text != null &&
    this.state.text.isPaintBoundsInverted()
  ) {
    bounds = bounds.clone();
    var tmp = bounds.width;
    bounds.width = bounds.height;
    bounds.height = tmp;
  }

  var m = this.getLabelMargins(bounds);

  if (m != null) {
    var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, false) == '1';
    var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, false) == '1';

    // Handles special case for vertical labels
    if (this.state != null && this.state.text != null && this.state.text.isPaintBoundsInverted()) {
      var tmp = m.x;
      m.x = m.height;
      m.height = m.width;
      m.width = m.y;
      m.y = tmp;

      tmp = flipH;
      flipH = flipV;
      flipV = tmp;
    }

    return mxUtils.getDirectedBounds(rect, m, this.style, flipH, flipV);
  }

  return rect;
};

/**
 * Function: getLabelMargins
 *
 * Returns the scaled top, left, bottom and right margin to be used for
 * computing the label bounds as an <mxRectangle>, where the bottom and right
 * margin are defined in the width and height of the rectangle, respectively.
 */
mxShape.prototype.getLabelMargins = function (rect) {
  return null;
};

/**
 * Function: checkBounds
 *
 * Returns true if the bounds are not null and all of its variables are numeric.
 */
mxShape.prototype.checkBounds = function () {
  return (
    !isNaN(this.scale) &&
    isFinite(this.scale) &&
    this.scale > 0 &&
    this.bounds != null &&
    !isNaN(this.bounds.x) &&
    !isNaN(this.bounds.y) &&
    !isNaN(this.bounds.width) &&
    !isNaN(this.bounds.height) &&
    this.bounds.width > 0 &&
    this.bounds.height > 0
  );
};

/**
 * Function: createVmlGroup
 *
 * Returns the temporary element used for rendering in IE8 standards mode.
 */
mxShape.prototype.createVmlGroup = function () {
  var node = document.createElement(mxClient.VML_PREFIX + ':group');
  node.style.position = 'absolute';
  node.style.width = this.node.style.width;
  node.style.height = this.node.style.height;

  return node;
};

/**
 * Function: redrawShape
 *
 * Updates the SVG or VML shape.
 */
mxShape.prototype.redrawShape = function () {
  var canvas = this.createCanvas();

  if (canvas != null) {
    // Specifies if events should be handled
    canvas.pointerEvents = this.pointerEvents;

    this.beforePaint(canvas);
    this.paint(canvas);
    this.afterPaint(canvas);

    if (this.node != canvas.root) {
      // Forces parsing in IE8 standards mode - slow! avoid
      this.node.insertAdjacentHTML('beforeend', canvas.root.outerHTML);
    }

    if (this.node.nodeName == 'DIV' && document.documentMode == 8) {
      // Makes DIV transparent to events for IE8 in IE8 standards
      // mode (Note: Does not work for IE9 in IE8 standards mode
      // and not for IE11 in enterprise mode)
      this.node.style.filter = '';

      // Adds event transparency in IE8 standards
      mxUtils.addTransparentBackgroundFilter(this.node);
    }

    this.destroyCanvas(canvas);
  }
};

/**
 * Function: createCanvas
 *
 * Creates a new canvas for drawing this shape. May return null.
 */
mxShape.prototype.createCanvas = function () {
  var canvas = null;

  // LATER: Check if reusing existing DOM nodes improves performance
  if (this.node.ownerSVGElement != null) {
    canvas = this.createSvgCanvas();
  } else if (mxClient.IS_VML) {
    this.updateVmlContainer();
    canvas = this.createVmlCanvas();
  }

  if (canvas != null && this.outline) {
    canvas.setStrokeWidth(this.strokewidth);
    canvas.setStrokeColor(this.stroke);

    if (this.isDashed != null) {
      canvas.setDashed(this.isDashed);
    }

    canvas.setStrokeWidth = function () {};
    canvas.setStrokeColor = function () {};
    canvas.setFillColor = function () {};
    canvas.setGradient = function () {};
    canvas.setDashed = function () {};
    canvas.text = function () {};
  }

  return canvas;
};

/**
 * Function: createSvgCanvas
 *
 * Creates and returns an <mxSvgCanvas2D> for rendering this shape.
 */
mxShape.prototype.createSvgCanvas = function () {
  var canvas = new mxSvgCanvas2D(this.node, false);
  canvas.strokeTolerance = this.pointerEvents ? this.svgStrokeTolerance : 0;
  canvas.pointerEventsValue = this.svgPointerEvents;
  var off = this.getSvgScreenOffset();

  if (off != 0) {
    this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')');
  } else {
    this.node.removeAttribute('transform');
  }

  canvas.minStrokeWidth = this.minSvgStrokeWidth;

  if (!this.antiAlias) {
    // Rounds all numbers in the SVG output to integers
    canvas.format = function (value) {
      return Math.round(parseFloat(value));
    };
  }

  return canvas;
};

/**
 * Function: createVmlCanvas
 *
 * Creates and returns an <mxVmlCanvas2D> for rendering this shape.
 */
mxShape.prototype.createVmlCanvas = function () {
  // Workaround for VML rendering bug in IE8 standards mode
  var node = document.documentMode == 8 && this.isParseVml() ? this.createVmlGroup() : this.node;
  var canvas = new mxVmlCanvas2D(node, false);

  if (node.tagUrn != '') {
    var w = Math.max(1, Math.round(this.bounds.width));
    var h = Math.max(1, Math.round(this.bounds.height));
    node.coordsize = w * this.vmlScale + ',' + h * this.vmlScale;
    canvas.scale(this.vmlScale);
    canvas.vmlScale = this.vmlScale;
  }

  // Painting relative to top, left shape corner
  var s = this.scale;
  canvas.translate(-Math.round(this.bounds.x / s), -Math.round(this.bounds.y / s));

  return canvas;
};

/**
 * Function: updateVmlContainer
 *
 * Updates the bounds of the VML container.
 */
mxShape.prototype.updateVmlContainer = function () {
  this.node.style.left = Math.round(this.bounds.x) + 'px';
  this.node.style.top = Math.round(this.bounds.y) + 'px';
  var w = Math.max(1, Math.round(this.bounds.width));
  var h = Math.max(1, Math.round(this.bounds.height));
  this.node.style.width = w + 'px';
  this.node.style.height = h + 'px';
  this.node.style.overflow = 'visible';
};

/**
 * Function: redrawHtml
 *
 * Allow optimization by replacing VML with HTML.
 */
mxShape.prototype.redrawHtmlShape = function () {
  // LATER: Refactor methods
  this.updateHtmlBounds(this.node);
  this.updateHtmlFilters(this.node);
  this.updateHtmlColors(this.node);
};

/**
 * Function: updateHtmlFilters
 *
 * Allow optimization by replacing VML with HTML.
 */
mxShape.prototype.updateHtmlFilters = function (node) {
  var f = '';

  if (this.opacity < 100) {
    f += 'alpha(opacity=' + this.opacity + ')';
  }

  if (this.isShadow) {
    // FIXME: Cannot implement shadow transparency with filter
    f +=
      'progid:DXImageTransform.Microsoft.dropShadow (' +
      "OffX='" +
      Math.round(mxConstants.SHADOW_OFFSET_X * this.scale) +
      "', " +
      "OffY='" +
      Math.round(mxConstants.SHADOW_OFFSET_Y * this.scale) +
      "', " +
      "Color='" +
      mxConstants.VML_SHADOWCOLOR +
      "')";
  }

  if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE) {
    var start = this.fill;
    var end = this.gradient;
    var type = '0';

    var lookup = { east: 0, south: 1, west: 2, north: 3 };
    var dir = this.direction != null ? lookup[this.direction] : 0;

    if (this.gradientDirection != null) {
      dir = mxUtils.mod(dir + lookup[this.gradientDirection] - 1, 4);
    }

    if (dir == 1) {
      type = '1';
      var tmp = start;
      start = end;
      end = tmp;
    } else if (dir == 2) {
      var tmp = start;
      start = end;
      end = tmp;
    } else if (dir == 3) {
      type = '1';
    }

    f +=
      'progid:DXImageTransform.Microsoft.gradient(' +
      "startColorStr='" +
      start +
      "', endColorStr='" +
      end +
      "', gradientType='" +
      type +
      "')";
  }

  node.style.filter = f;
};

/**
 * Function: updateHtmlColors
 *
 * Allow optimization by replacing VML with HTML.
 */
mxShape.prototype.updateHtmlColors = function (node) {
  var color = this.stroke;

  if (color != null && color != mxConstants.NONE) {
    node.style.borderColor = color;

    if (this.isDashed) {
      node.style.borderStyle = 'dashed';
    } else if (this.strokewidth > 0) {
      node.style.borderStyle = 'solid';
    }

    node.style.borderWidth = Math.max(1, Math.ceil(this.strokewidth * this.scale)) + 'px';
  } else {
    node.style.borderWidth = '0px';
  }

  color = this.outline ? null : this.fill;

  if (color != null && color != mxConstants.NONE) {
    node.style.backgroundColor = color;
    node.style.backgroundImage = 'none';
  } else if (this.pointerEvents) {
    node.style.backgroundColor = 'transparent';
  } else if (document.documentMode == 8) {
    mxUtils.addTransparentBackgroundFilter(node);
  } else {
    this.setTransparentBackgroundImage(node);
  }
};

/**
 * Function: updateHtmlBounds
 *
 * Allow optimization by replacing VML with HTML.
 */
mxShape.prototype.updateHtmlBounds = function (node) {
  var sw = document.documentMode >= 9 ? 0 : Math.ceil(this.strokewidth * this.scale);
  node.style.borderWidth = Math.max(1, sw) + 'px';
  node.style.overflow = 'hidden';

  node.style.left = Math.round(this.bounds.x - sw / 2) + 'px';
  node.style.top = Math.round(this.bounds.y - sw / 2) + 'px';

  if (document.compatMode == 'CSS1Compat') {
    sw = -sw;
  }

  node.style.width = Math.round(Math.max(0, this.bounds.width + sw)) + 'px';
  node.style.height = Math.round(Math.max(0, this.bounds.height + sw)) + 'px';
};

/**
 * Function: destroyCanvas
 *
 * Destroys the given canvas which was used for drawing. This implementation
 * increments the reference counts on all shared gradients used in the canvas.
 */
mxShape.prototype.destroyCanvas = function (canvas) {
  // Manages reference counts
  if (canvas instanceof mxSvgCanvas2D) {
    // Increments ref counts
    for (var key in canvas.gradients) {
      var gradient = canvas.gradients[key];

      if (gradient != null) {
        gradient.mxRefCount = (gradient.mxRefCount || 0) + 1;
      }
    }

    this.releaseSvgGradients(this.oldGradients);
    this.oldGradients = canvas.gradients;
  }
};

/**
 * Function: beforePaint
 *
 * Invoked before paint is called.
 */
mxShape.prototype.beforePaint = function (c) {};

/**
 * Function: afterPaint
 *
 * Invokes after paint was called.
 */
mxShape.prototype.afterPaint = function (c) {};

/**
 * Function: paint
 *
 * Generic rendering code.
 */
mxShape.prototype.paint = function (c) {
  var strokeDrawn = false;

  if (c != null && this.outline) {
    var stroke = c.stroke;

    c.stroke = function () {
      strokeDrawn = true;
      stroke.apply(this, arguments);
    };

    var fillAndStroke = c.fillAndStroke;

    c.fillAndStroke = function () {
      strokeDrawn = true;
      fillAndStroke.apply(this, arguments);
    };
  }

  // Scale is passed-through to canvas
  var s = this.scale;
  var x = this.bounds.x / s;
  var y = this.bounds.y / s;
  var w = this.bounds.width / s;
  var h = this.bounds.height / s;

  if (this.isPaintBoundsInverted()) {
    var t = (w - h) / 2;
    x += t;
    y -= t;
    var tmp = w;
    w = h;
    h = tmp;
  }

  this.updateTransform(c, x, y, w, h);
  this.configureCanvas(c, x, y, w, h);

  // Adds background rectangle to capture events
  var bg = null;

  if (
    (this.stencil == null && this.points == null && this.shapePointerEvents) ||
    (this.stencil != null && this.stencilPointerEvents)
  ) {
    var bb = this.createBoundingBox();

    if (this.dialect == mxConstants.DIALECT_SVG) {
      bg = this.createTransparentSvgRectangle(bb.x, bb.y, bb.width, bb.height);
      this.node.appendChild(bg);
    } else {
      var rect = c.createRect('rect', bb.x / s, bb.y / s, bb.width / s, bb.height / s);
      rect.appendChild(c.createTransparentFill());
      rect.stroked = 'false';
      c.root.appendChild(rect);
    }
  }

  if (this.stencil != null) {
    this.stencil.drawShape(c, this, x, y, w, h);
  } else {
    // Stencils have separate strokewidth
    c.setStrokeWidth(this.strokewidth);

    if (this.points != null) {
      // Paints edge shape
      var pts = [];

      for (var i = 0; i < this.points.length; i++) {
        if (this.points[i] != null) {
          pts.push(new mxPoint(this.points[i].x / s, this.points[i].y / s));
        }
      }

      this.paintEdgeShape(c, pts);
    } else {
      // Paints vertex shape
      this.paintVertexShape(c, x, y, w, h);
    }
  }

  if (bg != null && c.state != null && c.state.transform != null) {
    bg.setAttribute('transform', c.state.transform);
  }

  // Draws highlight rectangle if no stroke was used
  if (c != null && this.outline && !strokeDrawn) {
    c.rect(x, y, w, h);
    c.stroke();
  }
};

/**
 * Function: configureCanvas
 *
 * Sets the state of the canvas for drawing the shape.
 */
mxShape.prototype.configureCanvas = function (c, x, y, w, h) {
  var dash = null;

  if (this.style != null) {
    dash = this.style['dashPattern'];
  }

  c.setAlpha(this.opacity / 100);
  c.setFillAlpha(this.fillOpacity / 100);
  c.setStrokeAlpha(this.strokeOpacity / 100);

  // Sets alpha, colors and gradients
  if (this.isShadow != null) {
    c.setShadow(this.isShadow);
  }

  // Dash pattern
  if (this.isDashed != null) {
    c.setDashed(
      this.isDashed,
      this.style != null ? mxUtils.getValue(this.style, mxConstants.STYLE_FIX_DASH, false) == 1 : false
    );
  }

  if (dash != null) {
    c.setDashPattern(dash);
  }

  if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE) {
    var b = this.getGradientBounds(c, x, y, w, h);
    c.setGradient(this.fill, this.gradient, b.x, b.y, b.width, b.height, this.gradientDirection);
  } else {
    c.setFillColor(this.fill);
  }

  c.setStrokeColor(this.stroke);
};

/**
 * Function: getGradientBounds
 *
 * Returns the bounding box for the gradient box for this shape.
 */
mxShape.prototype.getGradientBounds = function (c, x, y, w, h) {
  return new mxRectangle(x, y, w, h);
};

/**
 * Function: updateTransform
 *
 * Sets the scale and rotation on the given canvas.
 */
mxShape.prototype.updateTransform = function (c, x, y, w, h) {
  // NOTE: Currently, scale is implemented in state and canvas. This will
  // move to canvas in a later version, so that the states are unscaled
  // and untranslated and do not need an update after zooming or panning.
  c.scale(this.scale);
  c.rotate(this.getShapeRotation(), this.flipH, this.flipV, x + w / 2, y + h / 2);
};

/**
 * Function: paintVertexShape
 *
 * Paints the vertex shape.
 */
mxShape.prototype.paintVertexShape = function (c, x, y, w, h) {
  this.paintBackground(c, x, y, w, h);

  if (
    !this.outline ||
    this.style == null ||
    mxUtils.getValue(this.style, mxConstants.STYLE_BACKGROUND_OUTLINE, 0) == 0
  ) {
    c.setShadow(false);
    this.paintForeground(c, x, y, w, h);
  }
};

/**
 * Function: paintBackground
 *
 * Hook for subclassers. This implementation is empty.
 */
mxShape.prototype.paintBackground = function (c, x, y, w, h) {};

/**
 * Function: paintForeground
 *
 * Hook for subclassers. This implementation is empty.
 */
mxShape.prototype.paintForeground = function (c, x, y, w, h) {};

/**
 * Function: paintEdgeShape
 *
 * Hook for subclassers. This implementation is empty.
 */
mxShape.prototype.paintEdgeShape = function (c, pts) {};

/**
 * Function: getArcSize
 *
 * Returns the arc size for the given dimension.
 */
mxShape.prototype.getArcSize = function (w, h) {
  var r = 0;

  if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') {
    r = Math.min(
      w / 2,
      Math.min(h / 2, mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2)
    );
  } else {
    var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
    r = Math.min(w * f, h * f);
  }

  return r;
};

/**
 * Function: paintGlassEffect
 *
 * Paints the glass gradient effect.
 */
mxShape.prototype.paintGlassEffect = function (c, x, y, w, h, arc) {
  var sw = Math.ceil(this.strokewidth / 2);
  var size = 0.4;

  c.setGradient('#ffffff', '#ffffff', x, y, w, h * 0.6, 'south', 0.9, 0.1);
  c.begin();
  arc += 2 * sw;

  if (this.isRounded) {
    c.moveTo(x - sw + arc, y - sw);
    c.quadTo(x - sw, y - sw, x - sw, y - sw + arc);
    c.lineTo(x - sw, y + h * size);
    c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size);
    c.lineTo(x + w + sw, y - sw + arc);
    c.quadTo(x + w + sw, y - sw, x + w + sw - arc, y - sw);
  } else {
    c.moveTo(x - sw, y - sw);
    c.lineTo(x - sw, y + h * size);
    c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size);
    c.lineTo(x + w + sw, y - sw);
  }

  c.close();
  c.fill();
};

/**
 * Function: addPoints
 *
 * Paints the given points with rounded corners.
 */
mxShape.prototype.addPoints = function (c, pts, rounded, arcSize, close, exclude, initialMove) {
  if (pts != null && pts.length > 0) {
    initialMove = initialMove != null ? initialMove : true;
    var pe = pts[pts.length - 1];

    // Adds virtual waypoint in the center between start and end point
    if (close && rounded) {
      pts = pts.slice();
      var p0 = pts[0];
      var wp = new mxPoint(pe.x + (p0.x - pe.x) / 2, pe.y + (p0.y - pe.y) / 2);
      pts.splice(0, 0, wp);
    }

    var pt = pts[0];
    var i = 1;

    // Draws the line segments
    if (initialMove) {
      c.moveTo(pt.x, pt.y);
    } else {
      c.lineTo(pt.x, pt.y);
    }

    while (i < (close ? pts.length : pts.length - 1)) {
      var tmp = pts[mxUtils.mod(i, pts.length)];
      var dx = pt.x - tmp.x;
      var dy = pt.y - tmp.y;

      if (rounded && (dx != 0 || dy != 0) && (exclude == null || mxUtils.indexOf(exclude, i - 1) < 0)) {
        // Draws a line from the last point to the current
        // point with a spacing of size off the current point
        // into direction of the last point
        var dist = Math.sqrt(dx * dx + dy * dy);
        var nx1 = (dx * Math.min(arcSize, dist / 2)) / dist;
        var ny1 = (dy * Math.min(arcSize, dist / 2)) / dist;

        var x1 = tmp.x + nx1;
        var y1 = tmp.y + ny1;
        c.lineTo(x1, y1);

        // Draws a curve from the last point to the current
        // point with a spacing of size off the current point
        // into direction of the next point
        var next = pts[mxUtils.mod(i + 1, pts.length)];

        // Uses next non-overlapping point
        while (i < pts.length - 2 && Math.round(next.x - tmp.x) == 0 && Math.round(next.y - tmp.y) == 0) {
          next = pts[mxUtils.mod(i + 2, pts.length)];
          i++;
        }

        dx = next.x - tmp.x;
        dy = next.y - tmp.y;

        dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
        var nx2 = (dx * Math.min(arcSize, dist / 2)) / dist;
        var ny2 = (dy * Math.min(arcSize, dist / 2)) / dist;

        var x2 = tmp.x + nx2;
        var y2 = tmp.y + ny2;

        c.quadTo(tmp.x, tmp.y, x2, y2);
        tmp = new mxPoint(x2, y2);
      } else {
        c.lineTo(tmp.x, tmp.y);
      }

      pt = tmp;
      i++;
    }

    if (close) {
      c.close();
    } else {
      c.lineTo(pe.x, pe.y);
    }
  }
};

/**
 * Function: resetStyles
 *
 * Resets all styles.
 */
mxShape.prototype.resetStyles = function () {
  this.initStyles();

  this.spacing = 0;

  delete this.fill;
  delete this.gradient;
  delete this.gradientDirection;
  delete this.stroke;
  delete this.startSize;
  delete this.endSize;
  delete this.startArrow;
  delete this.endArrow;
  delete this.direction;
  delete this.isShadow;
  delete this.isDashed;
  delete this.isRounded;
  delete this.glass;
};

/**
 * Function: apply
 *
 * Applies the style of the given <mxCellState> to the shape. This
 * implementation assigns the following styles to local fields:
 *
 * - <mxConstants.STYLE_FILLCOLOR> => fill
 * - <mxConstants.STYLE_GRADIENTCOLOR> => gradient
 * - <mxConstants.STYLE_GRADIENT_DIRECTION> => gradientDirection
 * - <mxConstants.STYLE_OPACITY> => opacity
 * - <mxConstants.STYLE_FILL_OPACITY> => fillOpacity
 * - <mxConstants.STYLE_STROKE_OPACITY> => strokeOpacity
 * - <mxConstants.STYLE_STROKECOLOR> => stroke
 * - <mxConstants.STYLE_STROKEWIDTH> => strokewidth
 * - <mxConstants.STYLE_SHADOW> => isShadow
 * - <mxConstants.STYLE_DASHED> => isDashed
 * - <mxConstants.STYLE_SPACING> => spacing
 * - <mxConstants.STYLE_STARTSIZE> => startSize
 * - <mxConstants.STYLE_ENDSIZE> => endSize
 * - <mxConstants.STYLE_ROUNDED> => isRounded
 * - <mxConstants.STYLE_STARTARROW> => startArrow
 * - <mxConstants.STYLE_ENDARROW> => endArrow
 * - <mxConstants.STYLE_ROTATION> => rotation
 * - <mxConstants.STYLE_DIRECTION> => direction
 * - <mxConstants.STYLE_GLASS> => glass
 *
 * This keeps a reference to the <style>. If you need to keep a reference to
 * the cell, you can override this method and store a local reference to
 * state.cell or the <mxCellState> itself. If <outline> should be true, make
 * sure to set it before calling this method.
 *
 * Parameters:
 *
 * state - <mxCellState> of the corresponding cell.
 */
mxShape.prototype.apply = function (state) {
  this.state = state;
  this.style = state.style;

  if (this.style != null) {
    this.fill = mxUtils.getValue(this.style, mxConstants.STYLE_FILLCOLOR, this.fill);
    this.gradient = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENTCOLOR, this.gradient);
    this.gradientDirection = mxUtils.getValue(this.style, mxConstants.STYLE_GRADIENT_DIRECTION, this.gradientDirection);
    this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_OPACITY, this.opacity);
    this.fillOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_FILL_OPACITY, this.fillOpacity);
    this.strokeOpacity = mxUtils.getValue(this.style, mxConstants.STYLE_STROKE_OPACITY, this.strokeOpacity);
    this.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_STROKECOLOR, this.stroke);
    this.strokewidth = mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth);
    this.spacing = mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing);
    this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, this.startSize);
    this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, this.endSize);
    this.startArrow = mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, this.startArrow);
    this.endArrow = mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, this.endArrow);
    this.rotation = mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, this.rotation);
    this.direction = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, this.direction);
    this.flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1;
    this.flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1;

    // Legacy support for stencilFlipH/V
    if (this.stencil != null) {
      this.flipH = mxUtils.getValue(this.style, 'stencilFlipH', 0) == 1 || this.flipH;
      this.flipV = mxUtils.getValue(this.style, 'stencilFlipV', 0) == 1 || this.flipV;
    }

    if (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH) {
      var tmp = this.flipH;
      this.flipH = this.flipV;
      this.flipV = tmp;
    }

    this.isShadow = mxUtils.getValue(this.style, mxConstants.STYLE_SHADOW, this.isShadow) == 1;
    this.isDashed = mxUtils.getValue(this.style, mxConstants.STYLE_DASHED, this.isDashed) == 1;
    this.isRounded = mxUtils.getValue(this.style, mxConstants.STYLE_ROUNDED, this.isRounded) == 1;
    this.glass = mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, this.glass) == 1;

    if (this.fill == mxConstants.NONE) {
      this.fill = null;
    }

    if (this.gradient == mxConstants.NONE) {
      this.gradient = null;
    }

    if (this.stroke == mxConstants.NONE) {
      this.stroke = null;
    }
  }
};

/**
 * Function: setCursor
 *
 * Sets the cursor on the given shape.
 *
 * Parameters:
 *
 * cursor - The cursor to be used.
 */
mxShape.prototype.setCursor = function (cursor) {
  if (cursor == null) {
    cursor = '';
  }

  this.cursor = cursor;

  if (this.node != null) {
    this.node.style.cursor = cursor;
  }
};

/**
 * Function: getCursor
 *
 * Returns the current cursor.
 */
mxShape.prototype.getCursor = function () {
  return this.cursor;
};

/**
 * Function: isRoundable
 *
 * Hook for subclassers.
 */
mxShape.prototype.isRoundable = function () {
  return false;
};

/**
 * Function: updateBoundingBox
 *
 * Updates the <boundingBox> for this shape using <createBoundingBox> and
 * <augmentBoundingBox> and stores the result in <boundingBox>.
 */
mxShape.prototype.updateBoundingBox = function () {
  // Tries to get bounding box from SVG subsystem
  // LATER: Use getBoundingClientRect for fallback in VML
  if (this.useSvgBoundingBox && this.node != null && this.node.ownerSVGElement != null) {
    try {
      var b = this.node.getBBox();

      if (b.width > 0 && b.height > 0) {
        this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);

        // Adds strokeWidth
        this.boundingBox.grow((this.strokewidth * this.scale) / 2);

        return;
      }
    } catch (e) {
      // fallback to code below
    }
  }

  if (this.bounds != null) {
    var bbox = this.createBoundingBox();

    if (bbox != null) {
      this.augmentBoundingBox(bbox);
      var rot = this.getShapeRotation();

      if (rot != 0) {
        bbox = mxUtils.getBoundingBox(bbox, rot);
      }
    }

    this.boundingBox = bbox;
  }
};

/**
 * Function: createBoundingBox
 *
 * Returns a new rectangle that represents the bounding box of the bare shape
 * with no shadows or strokewidths.
 */
mxShape.prototype.createBoundingBox = function () {
  var bb = this.bounds.clone();

  if (
    (this.stencil != null &&
      (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH)) ||
    this.isPaintBoundsInverted()
  ) {
    bb.rotate90();
  }

  return bb;
};

/**
 * Function: augmentBoundingBox
 *
 * Augments the bounding box with the strokewidth and shadow offsets.
 */
mxShape.prototype.augmentBoundingBox = function (bbox) {
  if (this.isShadow) {
    bbox.width += Math.ceil(mxConstants.SHADOW_OFFSET_X * this.scale);
    bbox.height += Math.ceil(mxConstants.SHADOW_OFFSET_Y * this.scale);
  }

  // Adds strokeWidth
  bbox.grow((this.strokewidth * this.scale) / 2);
};

/**
 * Function: isPaintBoundsInverted
 *
 * Returns true if the bounds should be inverted.
 */
mxShape.prototype.isPaintBoundsInverted = function () {
  // Stencil implements inversion via aspect
  return (
    this.stencil == null &&
    (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH)
  );
};

/**
 * Function: getRotation
 *
 * Returns the rotation from the style.
 */
mxShape.prototype.getRotation = function () {
  return this.rotation != null ? this.rotation : 0;
};

/**
 * Function: getTextRotation
 *
 * Returns the rotation for the text label.
 */
mxShape.prototype.getTextRotation = function () {
  var rot = this.getRotation();

  if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) != 1) {
    rot += mxText.prototype.verticalTextRotation;
  }

  return rot;
};

/**
 * Function: getShapeRotation
 *
 * Returns the actual rotation of the shape.
 */
mxShape.prototype.getShapeRotation = function () {
  var rot = this.getRotation();

  if (this.direction != null) {
    if (this.direction == mxConstants.DIRECTION_NORTH) {
      rot += 270;
    } else if (this.direction == mxConstants.DIRECTION_WEST) {
      rot += 180;
    } else if (this.direction == mxConstants.DIRECTION_SOUTH) {
      rot += 90;
    }
  }

  return rot;
};

/**
 * Function: createTransparentSvgRectangle
 *
 * Adds a transparent rectangle that catches all events.
 */
mxShape.prototype.createTransparentSvgRectangle = function (x, y, w, h) {
  var rect = document.createElementNS(mxConstants.NS_SVG, 'rect');
  rect.setAttribute('x', x);
  rect.setAttribute('y', y);
  rect.setAttribute('width', w);
  rect.setAttribute('height', h);
  rect.setAttribute('fill', 'none');
  rect.setAttribute('stroke', 'none');
  rect.setAttribute('pointer-events', 'all');

  return rect;
};

/**
 * Function: setTransparentBackgroundImage
 *
 * Sets a transparent background CSS style to catch all events.
 *
 * Paints the line shape.
 */
mxShape.prototype.setTransparentBackgroundImage = function (node) {
  node.style.backgroundImage = "url('" + mxClient.imageBasePath + "/transparent.gif')";
};

/**
 * Function: releaseSvgGradients
 *
 * Paints the line shape.
 */
mxShape.prototype.releaseSvgGradients = function (grads) {
  if (grads != null) {
    for (var key in grads) {
      var gradient = grads[key];

      if (gradient != null) {
        gradient.mxRefCount = (gradient.mxRefCount || 0) - 1;

        if (gradient.mxRefCount == 0 && gradient.parentNode != null) {
          gradient.parentNode.removeChild(gradient);
        }
      }
    }
  }
};

/**
 * Function: destroy
 *
 * Destroys the shape by removing it from the DOM and releasing the DOM
 * node associated with the shape using <mxEvent.release>.
 */
mxShape.prototype.destroy = function () {
  if (this.node != null) {
    mxEvent.release(this.node);

    if (this.node.parentNode != null) {
      this.node.parentNode.removeChild(this.node);
    }

    this.node = null;
  }

  // Decrements refCount and removes unused
  this.releaseSvgGradients(this.oldGradients);
  this.oldGradients = null;
};

/**
 * shape/mxStencil.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxStencil
 *
 * Implements a generic shape which is based on a XML node as a description.
 *
 * shape:
 *
 * The outer element is *shape*, that has attributes:
 *
 * - "name", string, required. The stencil name that uniquely identifies the shape.
 * - "w" and "h" are optional decimal view bounds. This defines your co-ordinate
 * system for the graphics operations in the shape. The default is 100,100.
 * - "aspect", optional string. Either "variable", the default, or "fixed". Fixed
 * means always render the shape with the aspect ratio defined by the ratio w/h.
 * Variable causes the ratio to match that of the geometry of the current vertex.
 * - "strokewidth", optional string. Either an integer or the string "inherit".
 * "inherit" indicates that the strokeWidth of the cell is only changed on scaling,
 * not on resizing. Default is "1".
 * If numeric values are used, the strokeWidth of the cell is changed on both
 * scaling and resizing and the value defines the multiple that is applied to
 * the width.
 *
 * connections:
 *
 * If you want to define specific fixed connection points on the shape use the
 * *connections* element. Each *constraint* element within connections defines
 * a fixed connection point on the shape. Constraints have attributes:
 *
 * - "perimeter", required. 1 or 0. 0 sets the connection point where specified
 * by x,y. 1 Causes the position of the connection point to be extrapolated from
 * the center of the shape, through x,y to the point of intersection with the
 * perimeter of the shape.
 * - "x" and "y" are the position of the fixed point relative to the bounds of
 * the shape. They can be automatically adjusted if perimeter=1. So, (0,0) is top
 * left, (0.5,0.5) the center, (1,0.5) the center of the right hand edge of the
 * bounds, etc. Values may be less than 0 or greater than 1 to be positioned
 * outside of the shape.
 * - "name", optional string. A unique identifier for the port on the shape.
 *
 * background and foreground:
 *
 * The path of the graphics drawing is split into two elements, *foreground* and
 * *background*. The split is to define which part any shadow applied to the shape
 * is derived from (the background). This, generally, means the background is the
 * line tracing of the outside of the shape, but not always.
 *
 * Any stroke, fill or fillstroke of a background must be the first element of the
 * foreground element, they must not be used within *background*. If the background
 * is empty, this is not required.
 *
 * Because the background cannot have any fill or stroke, it can contain only one
 * *path*, *rect*, *roundrect* or *ellipse* element (or none). It can also not
 * include *image*, *text* or *include-shape*.
 *
 * Note that the state, styling and drawing in mxGraph stencils is very close in
 * design to that of HTML 5 canvas. Tutorials on this subject, if you're not
 * familiar with the topic, will give a good high-level introduction to the
 * concepts used.
 *
 * State:
 *
 * Rendering within the foreground and background elements has the concept of
 * state. There are two types of operations other than state save/load, styling
 * and drawing. The styling operations change the current state, so you can save
 * the current state with <save/> and pull the last saved state from the state
 * stack using <restore/>.
 *
 * Styling:
 *
 * The elements that change colors within the current state all take a hash
 * prefixed hex color code ("#FFEA80").
 *
 * - *strokecolor*, this sets the color that drawing paths will be rendered in
 * when a stroke or fillstroke command is issued.
 * - *fillcolor*, this sets the color that the inside of closed paths will be
 * rendered in when a fill or fillstroke command is issued.
 * - *fontcolor*, this sets the color that fonts are rendered in when text is drawn.
 *
 * *alpha* defines the degree of transparency used between 1.0 for fully opaque
 * and 0.0 for fully transparent.
 *
 * *fillalpha* defines the degree of fill transparency used between 1.0 for fully
 * opaque and 0.0 for fully transparent.
 *
 * *strokealpha* defines the degree of stroke transparency used between 1.0 for
 * fully opaque and 0.0 for fully transparent.
 *
 * *strokewidth* defines the integer thickness of drawing elements rendered by
 * stroking. Use fixed="1" to apply the value as-is, without scaling.
 *
 * *dashed* is "1" for dashing enabled and "0" for disabled.
 *
 * When *dashed* is enabled the current dash pattern, defined by *dashpattern*,
 * is used on strokes. dashpattern is a sequence of space separated "on, off"
 * lengths that define what distance to paint the stroke for, then what distance
 * to paint nothing for, repeat... The default is "3 3". You could define a more
 * complex pattern with "5 3 2 6", for example. Generally, it makes sense to have
 * an even number of elements in the dashpattern, but that's not required.
 *
 * *linejoin*, *linecap* and *miterlimit* are best explained by the Mozilla page
 * on Canvas styling (about halfway down). The values are all the same except we
 * use "flat" for linecap, instead of Canvas' "butt".
 *
 * For font styling there are.
 *
 * - *fontsize*, an integer,
 * - *fontstyle*, an ORed bit pattern of bold (1), italic (2) and underline (4),
 * i.e bold underline is "5".
 * - *fontfamily*, is a string defining the typeface to be used.
 *
 * Drawing:
 *
 * Most drawing is contained within a *path* element. Again, the graphic
 * primitives are very similar to that of HTML 5 canvas.
 *
 * - *move* to attributes required decimals (x,y).
 * - *line* to attributes required decimals (x,y).
 * - *quad* to required decimals (x2,y2) via control point required decimals
 * (x1,y1).
 * - *curve* to required decimals (x3,y3), via control points required decimals
 * (x1,y1) and (x2,y2).
 * - *arc*, this doesn't follow the HTML Canvas signatures, instead it's a copy
 * of the SVG arc command. The SVG specification documentation gives the best
 * description of its behaviors. The attributes are named identically, they are
 * decimals and all required.
 * - *close* ends the current subpath and causes an automatic straight line to
 * be drawn from the current point to the initial point of the current subpath.
 *
 * Complex drawing:
 *
 * In addition to the graphics primitive operations there are non-primitive
 * operations. These provide an easy method to draw some basic shapes.
 *
 * - *rect*, attributes "x", "y", "w", "h", all required decimals
 * - *roundrect*, attributes "x", "y", "w", "h", all required decimals. Also
 * "arcsize" an optional decimal attribute defining how large, the corner curves
 * are.
 * - *ellipse*, attributes "x", "y", "w", "h", all required decimals.
 *
 * Note that these 3 shapes and all paths must be followed by either a fill,
 * stroke, or fillstroke.
 *
 * Text:
 *
 * *text* elements have the following attributes.
 *
 * - "str", the text string to display, required.
 * - "x" and "y", the decimal location (x,y) of the text element, required.
 * - "align", the horizontal alignment of the text element, either "left",
 * "center" or "right". Optional, default is "left".
 * - "valign", the vertical alignment of the text element, either "top", "middle"
 * or "bottom". Optional, default is "top".
 * - "localized", 0 or 1, if 1 then the "str" actually contains a key to use to
 * fetch the value out of mxResources. Optional, default is
 * <mxStencil.defaultLocalized>.
 * - "vertical", 0 or 1, if 1 the label is rendered vertically (rotated by 90
 * degrees). Optional, default is 0.
 * - "rotation", angle in degrees (0 to 360). The angle to rotate the text by.
 * Optional, default is 0.
 * - "align-shape", 0 or 1, if 0 ignore the rotation of the shape when setting
 * the text rotation. Optional, default is 1.
 *
 * If <allowEval> is true, then the text content of the this element can define
 * a function which is invoked with the shape as the only argument and returns
 * the value for the text element (ignored if the str attribute is not null).
 *
 * Images:
 *
 * *image* elements can either be external URLs, or data URIs, where supported
 * (not in IE 7-). Attributes are:
 *
 * - "src", required string. Either a data URI or URL.
 * - "x", "y", required decimals. The (x,y) position of the image.
 * - "w", "h", required decimals. The width and height of the image.
 * - "flipH" and "flipV", optional 0 or 1. Whether to flip the image along the
 * horizontal/vertical axis. Default is 0 for both.
 *
 * If <allowEval> is true, then the text content of the this element can define
 * a function which is invoked with the shape as the only argument and returns
 * the value for the image source (ignored if the src attribute is not null).
 *
 * Sub-shapes:
 *
 * *include-shape* allow stencils to be rendered within the current stencil by
 * referencing the sub-stencil by name. Attributes are:
 *
 * - "name", required string. The unique shape name of the stencil.
 * - "x", "y", "w", "h", required decimals. The (x,y) position of the sub-shape
 * and its width and height.
 *
 * Constructor: mxStencil
 *
 * Constructs a new generic shape by setting <desc> to the given XML node and
 * invoking <parseDescription> and <parseConstraints>.
 *
 * Parameters:
 *
 * desc - XML node that contains the stencil description.
 */
function mxStencil(desc) {
  this.desc = desc;
  this.parseDescription();
  this.parseConstraints();
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxStencil, mxShape);

/**
 * Variable: defaultLocalized
 *
 * Static global variable that specifies the default value for the localized
 * attribute of the text element. Default is false.
 */
mxStencil.defaultLocalized = false;

/**
 * Function: allowEval
 *
 * Static global switch that specifies if the use of eval is allowed for
 * evaluating text content and images. Default is false. Set this to true
 * if stencils can not contain user input.
 */
mxStencil.allowEval = false;

/**
 * Variable: desc
 *
 * Holds the XML node with the stencil description.
 */
mxStencil.prototype.desc = null;

/**
 * Variable: constraints
 *
 * Holds an array of <mxConnectionConstraints> as defined in the shape.
 */
mxStencil.prototype.constraints = null;

/**
 * Variable: aspect
 *
 * Holds the aspect of the shape. Default is 'auto'.
 */
mxStencil.prototype.aspect = null;

/**
 * Variable: w0
 *
 * Holds the width of the shape. Default is 100.
 */
mxStencil.prototype.w0 = null;

/**
 * Variable: h0
 *
 * Holds the height of the shape. Default is 100.
 */
mxStencil.prototype.h0 = null;

/**
 * Variable: bgNodes
 *
 * Holds the XML node with the stencil description.
 */
mxStencil.prototype.bgNode = null;

/**
 * Variable: fgNodes
 *
 * Holds the XML node with the stencil description.
 */
mxStencil.prototype.fgNode = null;

/**
 * Variable: strokewidth
 *
 * Holds the strokewidth direction from the description.
 */
mxStencil.prototype.strokewidth = null;

/**
 * Function: parseDescription
 *
 * Reads <w0>, <h0>, <aspect>, <bgNodes> and <fgNodes> from <desc>.
 */
mxStencil.prototype.parseDescription = function () {
  // LATER: Preprocess nodes for faster painting
  this.fgNode = this.desc.getElementsByTagName('foreground')[0];
  this.bgNode = this.desc.getElementsByTagName('background')[0];
  this.w0 = Number(this.desc.getAttribute('w') || 100);
  this.h0 = Number(this.desc.getAttribute('h') || 100);

  // Possible values for aspect are: variable and fixed where
  // variable means fill the available space and fixed means
  // use w0 and h0 to compute the aspect.
  var aspect = this.desc.getAttribute('aspect');
  this.aspect = aspect != null ? aspect : 'variable';

  // Possible values for strokewidth are all numbers and "inherit"
  // where the inherit means take the value from the style (ie. the
  // user-defined stroke-width). Note that the strokewidth is scaled
  // by the minimum scaling that is used to draw the shape (sx, sy).
  var sw = this.desc.getAttribute('strokewidth');
  this.strokewidth = sw != null ? sw : '1';
};

/**
 * Function: parseConstraints
 *
 * Reads the constraints from <desc> into <constraints> using
 * <parseConstraint>.
 */
mxStencil.prototype.parseConstraints = function () {
  var conns = this.desc.getElementsByTagName('connections')[0];

  if (conns != null) {
    var tmp = mxUtils.getChildNodes(conns);

    if (tmp != null && tmp.length > 0) {
      this.constraints = [];

      for (var i = 0; i < tmp.length; i++) {
        this.constraints.push(this.parseConstraint(tmp[i]));
      }
    }
  }
};

/**
 * Function: parseConstraint
 *
 * Parses the given XML node and returns its <mxConnectionConstraint>.
 */
mxStencil.prototype.parseConstraint = function (node) {
  var x = Number(node.getAttribute('x'));
  var y = Number(node.getAttribute('y'));
  var perimeter = node.getAttribute('perimeter') == '1';
  var name = node.getAttribute('name');

  return new mxConnectionConstraint(new mxPoint(x, y), perimeter, name);
};

/**
 * Function: evaluateTextAttribute
 *
 * Gets the given attribute as a text. The return value from <evaluateAttribute>
 * is used as a key to <mxResources.get> if the localized attribute in the text
 * node is 1 or if <defaultLocalized> is true.
 */
mxStencil.prototype.evaluateTextAttribute = function (node, attribute, shape) {
  var result = this.evaluateAttribute(node, attribute, shape);
  var loc = node.getAttribute('localized');

  if ((mxStencil.defaultLocalized && loc == null) || loc == '1') {
    result = mxResources.get(result);
  }

  return result;
};

/**
 * Function: evaluateAttribute
 *
 * Gets the attribute for the given name from the given node. If the attribute
 * does not exist then the text content of the node is evaluated and if it is
 * a function it is invoked with <shape> as the only argument and the return
 * value is used as the attribute value to be returned.
 */
mxStencil.prototype.evaluateAttribute = function (node, attribute, shape) {
  var result = node.getAttribute(attribute);

  if (result == null) {
    var text = mxUtils.getTextContent(node);

    if (text != null && mxStencil.allowEval) {
      var funct = mxUtils.eval(text);

      if (typeof funct == 'function') {
        result = funct(shape);
      }
    }
  }

  return result;
};

/**
 * Function: drawShape
 *
 * Draws this stencil inside the given bounds.
 */
mxStencil.prototype.drawShape = function (canvas, shape, x, y, w, h) {
  var stack = canvas.states.slice();

  // TODO: Internal structure (array of special structs?), relative and absolute
  // coordinates (eg. note shape, process vs star, actor etc.), text rendering
  // and non-proportional scaling, how to implement pluggable edge shapes
  // (start, segment, end blocks), pluggable markers, how to implement
  // swimlanes (title area) with this API, add icon, horizontal/vertical
  // label, indicator for all shapes, rotation
  var direction = mxUtils.getValue(shape.style, mxConstants.STYLE_DIRECTION, null);
  var aspect = this.computeAspect(shape.style, x, y, w, h, direction);
  var minScale = Math.min(aspect.width, aspect.height);
  var sw =
    this.strokewidth == 'inherit'
      ? Number(mxUtils.getNumber(shape.style, mxConstants.STYLE_STROKEWIDTH, 1))
      : Number(this.strokewidth) * minScale;
  canvas.setStrokeWidth(sw);

  // Draws a transparent rectangle for catching events
  if (shape.style != null && mxUtils.getValue(shape.style, mxConstants.STYLE_POINTER_EVENTS, '0') == '1') {
    canvas.setStrokeColor(mxConstants.NONE);
    canvas.rect(x, y, w, h);
    canvas.stroke();
    canvas.setStrokeColor(shape.stroke);
  }

  this.drawChildren(canvas, shape, x, y, w, h, this.bgNode, aspect, false, true);
  this.drawChildren(
    canvas,
    shape,
    x,
    y,
    w,
    h,
    this.fgNode,
    aspect,
    true,
    !shape.outline || shape.style == null || mxUtils.getValue(shape.style, mxConstants.STYLE_BACKGROUND_OUTLINE, 0) == 0
  );

  // Restores stack for unequal count of save/restore calls
  if (canvas.states.length != stack.length) {
    canvas.states = stack;
  }
};

/**
 * Function: drawChildren
 *
 * Draws this stencil inside the given bounds.
 */
mxStencil.prototype.drawChildren = function (canvas, shape, x, y, w, h, node, aspect, disableShadow, paint) {
  if (node != null && w > 0 && h > 0) {
    var tmp = node.firstChild;

    while (tmp != null) {
      if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT) {
        this.drawNode(canvas, shape, tmp, aspect, disableShadow, paint);
      }

      tmp = tmp.nextSibling;
    }
  }
};

/**
 * Function: computeAspect
 *
 * Returns a rectangle that contains the offset in x and y and the horizontal
 * and vertical scale in width and height used to draw this shape inside the
 * given <mxRectangle>.
 *
 * Parameters:
 *
 * shape - <mxShape> to be drawn.
 * bounds - <mxRectangle> that should contain the stencil.
 * direction - Optional direction of the shape to be darwn.
 */
mxStencil.prototype.computeAspect = function (shape, x, y, w, h, direction) {
  var x0 = x;
  var y0 = y;
  var sx = w / this.w0;
  var sy = h / this.h0;

  var inverse = direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH;

  if (inverse) {
    sy = w / this.h0;
    sx = h / this.w0;

    var delta = (w - h) / 2;

    x0 += delta;
    y0 -= delta;
  }

  if (this.aspect == 'fixed') {
    sy = Math.min(sx, sy);
    sx = sy;

    // Centers the shape inside the available space
    if (inverse) {
      x0 += (h - this.w0 * sx) / 2;
      y0 += (w - this.h0 * sy) / 2;
    } else {
      x0 += (w - this.w0 * sx) / 2;
      y0 += (h - this.h0 * sy) / 2;
    }
  }

  return new mxRectangle(x0, y0, sx, sy);
};

/**
 * Function: drawNode
 *
 * Draws this stencil inside the given bounds.
 */
mxStencil.prototype.drawNode = function (canvas, shape, node, aspect, disableShadow, paint) {
  var name = node.nodeName;
  var x0 = aspect.x;
  var y0 = aspect.y;
  var sx = aspect.width;
  var sy = aspect.height;
  var minScale = Math.min(sx, sy);

  if (name == 'save') {
    canvas.save();
  } else if (name == 'restore') {
    canvas.restore();
  } else if (paint) {
    if (name == 'path') {
      canvas.begin();

      var parseRegularly = true;

      if (node.getAttribute('rounded') == '1') {
        parseRegularly = false;

        var arcSize = Number(node.getAttribute('arcSize'));
        var pointCount = 0;
        var segs = [];

        // Renders the elements inside the given path
        var childNode = node.firstChild;

        while (childNode != null) {
          if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT) {
            var childName = childNode.nodeName;

            if (childName == 'move' || childName == 'line') {
              if (childName == 'move' || segs.length == 0) {
                segs.push([]);
              }

              segs[segs.length - 1].push(
                new mxPoint(
                  x0 + Number(childNode.getAttribute('x')) * sx,
                  y0 + Number(childNode.getAttribute('y')) * sy
                )
              );
              pointCount++;
            } else {
              //We only support move and line for rounded corners
              parseRegularly = true;
              break;
            }
          }

          childNode = childNode.nextSibling;
        }

        if (!parseRegularly && pointCount > 0) {
          for (var i = 0; i < segs.length; i++) {
            var close = false,
              ps = segs[i][0],
              pe = segs[i][segs[i].length - 1];

            if (ps.x == pe.x && ps.y == pe.y) {
              segs[i].pop();
              close = true;
            }

            this.addPoints(canvas, segs[i], true, arcSize, close);
          }
        } else {
          parseRegularly = true;
        }
      }

      if (parseRegularly) {
        // Renders the elements inside the given path
        var childNode = node.firstChild;

        while (childNode != null) {
          if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT) {
            this.drawNode(canvas, shape, childNode, aspect, disableShadow, paint);
          }

          childNode = childNode.nextSibling;
        }
      }
    } else if (name == 'close') {
      canvas.close();
    } else if (name == 'move') {
      canvas.moveTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);
    } else if (name == 'line') {
      canvas.lineTo(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy);
    } else if (name == 'quad') {
      canvas.quadTo(
        x0 + Number(node.getAttribute('x1')) * sx,
        y0 + Number(node.getAttribute('y1')) * sy,
        x0 + Number(node.getAttribute('x2')) * sx,
        y0 + Number(node.getAttribute('y2')) * sy
      );
    } else if (name == 'curve') {
      canvas.curveTo(
        x0 + Number(node.getAttribute('x1')) * sx,
        y0 + Number(node.getAttribute('y1')) * sy,
        x0 + Number(node.getAttribute('x2')) * sx,
        y0 + Number(node.getAttribute('y2')) * sy,
        x0 + Number(node.getAttribute('x3')) * sx,
        y0 + Number(node.getAttribute('y3')) * sy
      );
    } else if (name == 'arc') {
      canvas.arcTo(
        Number(node.getAttribute('rx')) * sx,
        Number(node.getAttribute('ry')) * sy,
        Number(node.getAttribute('x-axis-rotation')),
        Number(node.getAttribute('large-arc-flag')),
        Number(node.getAttribute('sweep-flag')),
        x0 + Number(node.getAttribute('x')) * sx,
        y0 + Number(node.getAttribute('y')) * sy
      );
    } else if (name == 'rect') {
      canvas.rect(
        x0 + Number(node.getAttribute('x')) * sx,
        y0 + Number(node.getAttribute('y')) * sy,
        Number(node.getAttribute('w')) * sx,
        Number(node.getAttribute('h')) * sy
      );
    } else if (name == 'roundrect') {
      var arcsize = Number(node.getAttribute('arcsize'));

      if (arcsize == 0) {
        arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
      }

      var w = Number(node.getAttribute('w')) * sx;
      var h = Number(node.getAttribute('h')) * sy;
      var factor = Number(arcsize) / 100;
      var r = Math.min(w * factor, h * factor);

      canvas.roundrect(x0 + Number(node.getAttribute('x')) * sx, y0 + Number(node.getAttribute('y')) * sy, w, h, r, r);
    } else if (name == 'ellipse') {
      canvas.ellipse(
        x0 + Number(node.getAttribute('x')) * sx,
        y0 + Number(node.getAttribute('y')) * sy,
        Number(node.getAttribute('w')) * sx,
        Number(node.getAttribute('h')) * sy
      );
    } else if (name == 'image') {
      if (!shape.outline) {
        var src = this.evaluateAttribute(node, 'src', shape);

        canvas.image(
          x0 + Number(node.getAttribute('x')) * sx,
          y0 + Number(node.getAttribute('y')) * sy,
          Number(node.getAttribute('w')) * sx,
          Number(node.getAttribute('h')) * sy,
          src,
          false,
          node.getAttribute('flipH') == '1',
          node.getAttribute('flipV') == '1'
        );
      }
    } else if (name == 'text') {
      if (!shape.outline) {
        var str = this.evaluateTextAttribute(node, 'str', shape);
        var rotation = node.getAttribute('vertical') == '1' ? -90 : 0;

        if (node.getAttribute('align-shape') == '0') {
          var dr = shape.rotation;

          // Depends on flipping
          var flipH = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPH, 0) == 1;
          var flipV = mxUtils.getValue(shape.style, mxConstants.STYLE_FLIPV, 0) == 1;

          if (flipH && flipV) {
            rotation -= dr;
          } else if (flipH || flipV) {
            rotation += dr;
          } else {
            rotation -= dr;
          }
        }

        rotation -= node.getAttribute('rotation');

        canvas.text(
          x0 + Number(node.getAttribute('x')) * sx,
          y0 + Number(node.getAttribute('y')) * sy,
          0,
          0,
          str,
          node.getAttribute('align') || 'left',
          node.getAttribute('valign') || 'top',
          false,
          '',
          null,
          false,
          rotation
        );
      }
    } else if (name == 'include-shape') {
      var stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));

      if (stencil != null) {
        var x = x0 + Number(node.getAttribute('x')) * sx;
        var y = y0 + Number(node.getAttribute('y')) * sy;
        var w = Number(node.getAttribute('w')) * sx;
        var h = Number(node.getAttribute('h')) * sy;

        stencil.drawShape(canvas, shape, x, y, w, h);
      }
    } else if (name == 'fillstroke') {
      canvas.fillAndStroke();
    } else if (name == 'fill') {
      canvas.fill();
    } else if (name == 'stroke') {
      canvas.stroke();
    } else if (name == 'strokewidth') {
      var s = node.getAttribute('fixed') == '1' ? 1 : minScale;
      canvas.setStrokeWidth(Number(node.getAttribute('width')) * s);
    } else if (name == 'dashed') {
      canvas.setDashed(node.getAttribute('dashed') == '1');
    } else if (name == 'dashpattern') {
      var value = node.getAttribute('pattern');

      if (value != null) {
        var tmp = value.split(' ');
        var pat = [];

        for (var i = 0; i < tmp.length; i++) {
          if (tmp[i].length > 0) {
            pat.push(Number(tmp[i]) * minScale);
          }
        }

        value = pat.join(' ');
        canvas.setDashPattern(value);
      }
    } else if (name == 'strokecolor') {
      canvas.setStrokeColor(node.getAttribute('color'));
    } else if (name == 'linecap') {
      canvas.setLineCap(node.getAttribute('cap'));
    } else if (name == 'linejoin') {
      canvas.setLineJoin(node.getAttribute('join'));
    } else if (name == 'miterlimit') {
      canvas.setMiterLimit(Number(node.getAttribute('limit')));
    } else if (name == 'fillcolor') {
      canvas.setFillColor(node.getAttribute('color'));
    } else if (name == 'alpha') {
      canvas.setAlpha(node.getAttribute('alpha'));
    } else if (name == 'fillalpha') {
      canvas.setAlpha(node.getAttribute('alpha'));
    } else if (name == 'strokealpha') {
      canvas.setAlpha(node.getAttribute('alpha'));
    } else if (name == 'fontcolor') {
      canvas.setFontColor(node.getAttribute('color'));
    } else if (name == 'fontstyle') {
      canvas.setFontStyle(node.getAttribute('style'));
    } else if (name == 'fontfamily') {
      canvas.setFontFamily(node.getAttribute('family'));
    } else if (name == 'fontsize') {
      canvas.setFontSize(Number(node.getAttribute('size')) * minScale);
    }

    if (disableShadow && (name == 'fillstroke' || name == 'fill' || name == 'stroke')) {
      disableShadow = false;
      canvas.setShadow(false);
    }
  }
};

/**
 * shape/mxStencilRegistry.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 *
 * Code to add stencils.
 *
 * (code)
 * var req = mxUtils.load('test/stencils.xml');
 * var root = req.getDocumentElement();
 * var shape = root.firstChild;
 *
 * while (shape != null)
 * {
 * 	 if (shape.nodeType == mxConstants.NODETYPE_ELEMENT)
 *   {
 *     mxStencilRegistry.addStencil(shape.getAttribute('name'), new mxStencil(shape));
 *   }
 *
 *   shape = shape.nextSibling;
 * }
 * (end)
 */
var mxStencilRegistry = {
  /**
   * Class: mxStencilRegistry
   *
   * A singleton class that provides a registry for stencils and the methods
   * for painting those stencils onto a canvas or into a DOM.
   */
  stencils: {},

  /**
   * Function: addStencil
   *
   * Adds the given <mxStencil>.
   */
  addStencil: function (name, stencil) {
    mxStencilRegistry.stencils[name] = stencil;
  },

  /**
   * Function: getStencil
   *
   * Returns the <mxStencil> for the given name.
   */
  getStencil: function (name) {
    return mxStencilRegistry.stencils[name];
  },
};

/**
 * shape/mxMarker.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxMarker = {
  /**
   * Class: mxMarker
   *
   * A static class that implements all markers for VML and SVG using a
   * registry. NOTE: The signatures in this class will change.
   *
   * Variable: markers
   *
   * Maps from markers names to functions to paint the markers.
   */
  markers: [],

  /**
   * Function: addMarker
   *
   * Adds a factory method that updates a given endpoint and returns a
   * function to paint the marker onto the given canvas.
   */
  addMarker: function (type, funct) {
    mxMarker.markers[type] = funct;
  },

  /**
   * Function: createMarker
   *
   * Returns a function to paint the given marker.
   */
  createMarker: function (canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    var funct = mxMarker.markers[type];

    return funct != null ? funct(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) : null;
  },
};

/**
 * Adds the classic and block marker factory method.
 */
(function () {
  function createArrow(widthFactor) {
    widthFactor = widthFactor != null ? widthFactor : 2;

    return function (canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) {
      // The angle of the forward facing arrow sides against the x axis is
      // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
      // only half the strokewidth is processed ).
      var endOffsetX = unitX * sw * 1.118;
      var endOffsetY = unitY * sw * 1.118;

      unitX = unitX * (size + sw);
      unitY = unitY * (size + sw);

      var pt = pe.clone();
      pt.x -= endOffsetX;
      pt.y -= endOffsetY;

      var f = type != mxConstants.ARROW_CLASSIC && type != mxConstants.ARROW_CLASSIC_THIN ? 1 : 3 / 4;
      pe.x += -unitX * f - endOffsetX;
      pe.y += -unitY * f - endOffsetY;

      return function () {
        canvas.begin();
        canvas.moveTo(pt.x, pt.y);
        canvas.lineTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);

        if (type == mxConstants.ARROW_CLASSIC || type == mxConstants.ARROW_CLASSIC_THIN) {
          canvas.lineTo(pt.x - (unitX * 3) / 4, pt.y - (unitY * 3) / 4);
        }

        canvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
        canvas.close();

        if (filled) {
          canvas.fillAndStroke();
        } else {
          canvas.stroke();
        }
      };
    };
  }

  mxMarker.addMarker('classic', createArrow(2));
  mxMarker.addMarker('classicThin', createArrow(3));
  mxMarker.addMarker('block', createArrow(2));
  mxMarker.addMarker('blockThin', createArrow(3));

  function createOpenArrow(widthFactor) {
    widthFactor = widthFactor != null ? widthFactor : 2;

    return function (canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) {
      // The angle of the forward facing arrow sides against the x axis is
      // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
      // only half the strokewidth is processed ).
      var endOffsetX = unitX * sw * 1.118;
      var endOffsetY = unitY * sw * 1.118;

      unitX = unitX * (size + sw);
      unitY = unitY * (size + sw);

      var pt = pe.clone();
      pt.x -= endOffsetX;
      pt.y -= endOffsetY;

      pe.x += -endOffsetX * 2;
      pe.y += -endOffsetY * 2;

      return function () {
        canvas.begin();
        canvas.moveTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);
        canvas.lineTo(pt.x, pt.y);
        canvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
        canvas.stroke();
      };
    };
  }

  mxMarker.addMarker('open', createOpenArrow(2));
  mxMarker.addMarker('openThin', createOpenArrow(3));

  mxMarker.addMarker('oval', function (canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    var a = size / 2;

    var pt = pe.clone();
    pe.x -= unitX * a;
    pe.y -= unitY * a;

    return function () {
      canvas.ellipse(pt.x - a, pt.y - a, size, size);

      if (filled) {
        canvas.fillAndStroke();
      } else {
        canvas.stroke();
      }
    };
  });

  function diamond(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    // The angle of the forward facing arrow sides against the x axis is
    // 45 degrees, 1/sin(45) = 1.4142 / 2 = 0.7071 ( / 2 allows for
    // only half the strokewidth is processed ). Or 0.9862 for thin diamond.
    // Note these values and the tk variable below are dependent, update
    // both together (saves trig hard coding it).
    var swFactor = type == mxConstants.ARROW_DIAMOND ? 0.7071 : 0.9862;
    var endOffsetX = unitX * sw * swFactor;
    var endOffsetY = unitY * sw * swFactor;

    unitX = unitX * (size + sw);
    unitY = unitY * (size + sw);

    var pt = pe.clone();
    pt.x -= endOffsetX;
    pt.y -= endOffsetY;

    pe.x += -unitX - endOffsetX;
    pe.y += -unitY - endOffsetY;

    // thickness factor for diamond
    var tk = type == mxConstants.ARROW_DIAMOND ? 2 : 3.4;

    return function () {
      canvas.begin();
      canvas.moveTo(pt.x, pt.y);
      canvas.lineTo(pt.x - unitX / 2 - unitY / tk, pt.y + unitX / tk - unitY / 2);
      canvas.lineTo(pt.x - unitX, pt.y - unitY);
      canvas.lineTo(pt.x - unitX / 2 + unitY / tk, pt.y - unitY / 2 - unitX / tk);
      canvas.close();

      if (filled) {
        canvas.fillAndStroke();
      } else {
        canvas.stroke();
      }
    };
  }

  mxMarker.addMarker('diamond', diamond);
  mxMarker.addMarker('diamondThin', diamond);
})();

/**
 * shape/mxActor.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxActor
 *
 * Extends <mxShape> to implement an actor shape. If a custom shape with one
 * filled area is needed, then this shape's <redrawPath> should be overridden.
 *
 * Example:
 *
 * (code)
 * function SampleShape() { }
 *
 * SampleShape.prototype = new mxActor();
 * SampleShape.prototype.constructor = vsAseShape;
 *
 * mxCellRenderer.registerShape('sample', SampleShape);
 * SampleShape.prototype.redrawPath = function(path, x, y, w, h)
 * {
 *   path.moveTo(0, 0);
 *   path.lineTo(w, h);
 *   // ...
 *   path.close();
 * }
 * (end)
 *
 * This shape is registered under <mxConstants.SHAPE_ACTOR> in
 * <mxCellRenderer>.
 *
 * Constructor: mxActor
 *
 * Constructs a new actor shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxActor(bounds, fill, stroke, strokewidth) {
  mxShape.call(this);
  this.bounds = bounds;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxActor, mxShape);

/**
 * Function: paintVertexShape
 *
 * Redirects to redrawPath for subclasses to work.
 */
mxActor.prototype.paintVertexShape = function (c, x, y, w, h) {
  c.translate(x, y);
  c.begin();
  this.redrawPath(c, x, y, w, h);
  c.fillAndStroke();
};

/**
 * Function: redrawPath
 *
 * Draws the path for this shape.
 */
mxActor.prototype.redrawPath = function (c, x, y, w, h) {
  var width = w / 3;
  c.moveTo(0, h);
  c.curveTo(0, (3 * h) / 5, 0, (2 * h) / 5, w / 2, (2 * h) / 5);
  c.curveTo(w / 2 - width, (2 * h) / 5, w / 2 - width, 0, w / 2, 0);
  c.curveTo(w / 2 + width, 0, w / 2 + width, (2 * h) / 5, w / 2, (2 * h) / 5);
  c.curveTo(w, (2 * h) / 5, w, (3 * h) / 5, w, h);
  c.close();
};

/**
 * shape/mxCloud.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCloud
 *
 * Extends <mxActor> to implement a cloud shape.
 *
 * This shape is registered under <mxConstants.SHAPE_CLOUD> in
 * <mxCellRenderer>.
 *
 * Constructor: mxCloud
 *
 * Constructs a new cloud shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxCloud(bounds, fill, stroke, strokewidth) {
  mxActor.call(this);
  this.bounds = bounds;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
}

/**
 * Extends mxActor.
 */
mxUtils.extend(mxCloud, mxActor);

/**
 * Function: redrawPath
 *
 * Draws the path for this shape.
 */
mxCloud.prototype.redrawPath = function (c, x, y, w, h) {
  c.moveTo(0.25 * w, 0.25 * h);
  c.curveTo(0.05 * w, 0.25 * h, 0, 0.5 * h, 0.16 * w, 0.55 * h);
  c.curveTo(0, 0.66 * h, 0.18 * w, 0.9 * h, 0.31 * w, 0.8 * h);
  c.curveTo(0.4 * w, h, 0.7 * w, h, 0.8 * w, 0.8 * h);
  c.curveTo(w, 0.8 * h, w, 0.6 * h, 0.875 * w, 0.5 * h);
  c.curveTo(w, 0.3 * h, 0.8 * w, 0.1 * h, 0.625 * w, 0.2 * h);
  c.curveTo(0.5 * w, 0.05 * h, 0.3 * w, 0.05 * h, 0.25 * w, 0.25 * h);
  c.close();
};

/**
 * shape/mxRectangleShape.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxRectangleShape
 *
 * Extends <mxShape> to implement a rectangle shape.
 * This shape is registered under <mxConstants.SHAPE_RECTANGLE>
 * in <mxCellRenderer>.
 *
 * Constructor: mxRectangleShape
 *
 * Constructs a new rectangle shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxRectangleShape(bounds, fill, stroke, strokewidth) {
  mxShape.call(this);
  this.bounds = bounds;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxRectangleShape, mxShape);

/**
 * Function: isHtmlAllowed
 *
 * Returns true for non-rounded, non-rotated shapes with no glass gradient.
 */
mxRectangleShape.prototype.isHtmlAllowed = function () {
  var events = true;

  if (this.style != null) {
    events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';
  }

  return (
    !this.isRounded &&
    !this.glass &&
    this.rotation == 0 &&
    (events || (this.fill != null && this.fill != mxConstants.NONE))
  );
};

/**
 * Function: paintBackground
 *
 * Generic background painting implementation.
 */
mxRectangleShape.prototype.paintBackground = function (c, x, y, w, h) {
  var events = true;

  if (this.style != null) {
    events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';
  }

  if (
    events ||
    (this.fill != null && this.fill != mxConstants.NONE) ||
    (this.stroke != null && this.stroke != mxConstants.NONE)
  ) {
    if (!events && (this.fill == null || this.fill == mxConstants.NONE)) {
      c.pointerEvents = false;
    }

    if (this.isRounded) {
      var r = 0;

      if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') {
        r = Math.min(
          w / 2,
          Math.min(h / 2, mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2)
        );
      } else {
        var f =
          mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
        r = Math.min(w * f, h * f);
      }

      c.roundrect(x, y, w, h, r, r);
    } else {
      c.rect(x, y, w, h);
    }

    c.fillAndStroke();
  }
};

/**
 * Function: isRoundable
 *
 * Adds roundable support.
 */
mxRectangleShape.prototype.isRoundable = function (c, x, y, w, h) {
  return true;
};

/**
 * Function: paintForeground
 *
 * Generic background painting implementation.
 */
mxRectangleShape.prototype.paintForeground = function (c, x, y, w, h) {
  if (this.glass && !this.outline && this.fill != null && this.fill != mxConstants.NONE) {
    this.paintGlassEffect(c, x, y, w, h, this.getArcSize(w + this.strokewidth, h + this.strokewidth));
  }
};

/**
 * shape/mxEllipse.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxEllipse
 *
 * Extends <mxShape> to implement an ellipse shape.
 * This shape is registered under <mxConstants.SHAPE_ELLIPSE>
 * in <mxCellRenderer>.
 *
 * Constructor: mxEllipse
 *
 * Constructs a new ellipse shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxEllipse(bounds, fill, stroke, strokewidth) {
  mxShape.call(this);
  this.bounds = bounds;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxEllipse, mxShape);

/**
 * Function: paintVertexShape
 *
 * Paints the ellipse shape.
 */
mxEllipse.prototype.paintVertexShape = function (c, x, y, w, h) {
  c.ellipse(x, y, w, h);
  c.fillAndStroke();
};

/**
 * shape/mxDoubleEllipse.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDoubleEllipse
 *
 * Extends <mxShape> to implement a double ellipse shape. This shape is
 * registered under <mxConstants.SHAPE_DOUBLE_ELLIPSE> in <mxCellRenderer>.
 * Use the following override to only fill the inner ellipse in this shape:
 *
 * (code)
 * mxDoubleEllipse.prototype.paintVertexShape = function(c, x, y, w, h)
 * {
 *   c.ellipse(x, y, w, h);
 *   c.stroke();
 *
 *   var inset = mxUtils.getValue(this.style, mxConstants.STYLE_MARGIN, Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5)));
 *   x += inset;
 *   y += inset;
 *   w -= 2 * inset;
 *   h -= 2 * inset;
 *
 *   if (w > 0 && h > 0)
 *   {
 *     c.ellipse(x, y, w, h);
 *   }
 *
 *   c.fillAndStroke();
 * };
 * (end)
 *
 * Constructor: mxDoubleEllipse
 *
 * Constructs a new ellipse shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxDoubleEllipse(bounds, fill, stroke, strokewidth) {
  mxShape.call(this);
  this.bounds = bounds;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxDoubleEllipse, mxShape);

/**
 * Variable: vmlScale
 *
 * Scale for improving the precision of VML rendering. Default is 10.
 */
mxDoubleEllipse.prototype.vmlScale = 10;

/**
 * Function: paintBackground
 *
 * Paints the background.
 */
mxDoubleEllipse.prototype.paintBackground = function (c, x, y, w, h) {
  c.ellipse(x, y, w, h);
  c.fillAndStroke();
};

/**
 * Function: paintForeground
 *
 * Paints the foreground.
 */
mxDoubleEllipse.prototype.paintForeground = function (c, x, y, w, h) {
  if (!this.outline) {
    var margin = mxUtils.getValue(
      this.style,
      mxConstants.STYLE_MARGIN,
      Math.min(3 + this.strokewidth, Math.min(w / 5, h / 5))
    );
    x += margin;
    y += margin;
    w -= 2 * margin;
    h -= 2 * margin;

    // FIXME: Rounding issues in IE8 standards mode (not in 1.x)
    if (w > 0 && h > 0) {
      c.ellipse(x, y, w, h);
    }

    c.stroke();
  }
};

/**
 * Function: getLabelBounds
 *
 * Returns the bounds for the label.
 */
mxDoubleEllipse.prototype.getLabelBounds = function (rect) {
  var margin =
    mxUtils.getValue(
      this.style,
      mxConstants.STYLE_MARGIN,
      Math.min(3 + this.strokewidth, Math.min(rect.width / 5 / this.scale, rect.height / 5 / this.scale))
    ) * this.scale;

  return new mxRectangle(rect.x + margin, rect.y + margin, rect.width - 2 * margin, rect.height - 2 * margin);
};

/**
 * shape/mxRhombus.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxRhombus
 *
 * Extends <mxShape> to implement a rhombus (aka diamond) shape.
 * This shape is registered under <mxConstants.SHAPE_RHOMBUS>
 * in <mxCellRenderer>.
 *
 * Constructor: mxRhombus
 *
 * Constructs a new rhombus shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxRhombus(bounds, fill, stroke, strokewidth) {
  mxShape.call(this);
  this.bounds = bounds;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxRhombus, mxShape);

/**
 * Function: isRoundable
 *
 * Adds roundable support.
 */
mxRhombus.prototype.isRoundable = function () {
  return true;
};

/**
 * Function: paintVertexShape
 *
 * Generic painting implementation.
 */
mxRhombus.prototype.paintVertexShape = function (c, x, y, w, h) {
  var hw = w / 2;
  var hh = h / 2;

  var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
  c.begin();
  this.addPoints(
    c,
    [new mxPoint(x + hw, y), new mxPoint(x + w, y + hh), new mxPoint(x + hw, y + h), new mxPoint(x, y + hh)],
    this.isRounded,
    arcSize,
    true
  );
  c.fillAndStroke();
};

/**
 * shape/mxPolyline.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPolyline
 *
 * Extends <mxShape> to implement a polyline (a line with multiple points).
 * This shape is registered under <mxConstants.SHAPE_POLYLINE> in
 * <mxCellRenderer>.
 *
 * Constructor: mxPolyline
 *
 * Constructs a new polyline shape.
 *
 * Parameters:
 *
 * points - Array of <mxPoints> that define the points. This is stored in
 * <mxShape.points>.
 * stroke - String that defines the stroke color. Default is 'black'. This is
 * stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxPolyline(points, stroke, strokewidth) {
  mxShape.call(this);
  this.points = points;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxPolyline, mxShape);

/**
 * Function: getRotation
 *
 * Returns 0.
 */
mxPolyline.prototype.getRotation = function () {
  return 0;
};

/**
 * Function: getShapeRotation
 *
 * Returns 0.
 */
mxPolyline.prototype.getShapeRotation = function () {
  return 0;
};

/**
 * Function: isPaintBoundsInverted
 *
 * Returns false.
 */
mxPolyline.prototype.isPaintBoundsInverted = function () {
  return false;
};

/**
 * Function: paintEdgeShape
 *
 * Paints the line shape.
 */
mxPolyline.prototype.paintEdgeShape = function (c, pts) {
  var prev = c.pointerEventsValue;
  c.pointerEventsValue = 'stroke';

  if (this.style == null || this.style[mxConstants.STYLE_CURVED] != 1) {
    this.paintLine(c, pts, this.isRounded);
  } else {
    this.paintCurvedLine(c, pts);
  }

  c.pointerEventsValue = prev;
};

/**
 * Function: paintLine
 *
 * Paints the line shape.
 */
mxPolyline.prototype.paintLine = function (c, pts, rounded) {
  var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
  c.begin();
  this.addPoints(c, pts, rounded, arcSize, false);
  c.stroke();
};

/**
 * Function: paintCurvedLine
 *
 * Paints a curved line.
 */
mxPolyline.prototype.paintCurvedLine = function (c, pts) {
  c.begin();

  var pt = pts[0];
  var n = pts.length;

  c.moveTo(pt.x, pt.y);

  for (var i = 1; i < n - 2; i++) {
    var p0 = pts[i];
    var p1 = pts[i + 1];
    var ix = (p0.x + p1.x) / 2;
    var iy = (p0.y + p1.y) / 2;

    c.quadTo(p0.x, p0.y, ix, iy);
  }

  var p0 = pts[n - 2];
  var p1 = pts[n - 1];

  c.quadTo(p0.x, p0.y, p1.x, p1.y);
  c.stroke();
};

/**
 * shape/mxArrow.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxArrow
 *
 * Extends <mxShape> to implement an arrow shape. (The shape
 * is used to represent edges, not vertices.)
 * This shape is registered under <mxConstants.SHAPE_ARROW>
 * in <mxCellRenderer>.
 *
 * Constructor: mxArrow
 *
 * Constructs a new arrow shape.
 *
 * Parameters:
 *
 * points - Array of <mxPoints> that define the points. This is stored in
 * <mxShape.points>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 * arrowWidth - Optional integer that defines the arrow width. Default is
 * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
 * spacing - Optional integer that defines the spacing between the arrow shape
 * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
 * <spacing>.
 * endSize - Optional integer that defines the size of the arrowhead. Default
 * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
 */
function mxArrow(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize) {
  mxShape.call(this);
  this.points = points;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
  this.arrowWidth = arrowWidth != null ? arrowWidth : mxConstants.ARROW_WIDTH;
  this.spacing = spacing != null ? spacing : mxConstants.ARROW_SPACING;
  this.endSize = endSize != null ? endSize : mxConstants.ARROW_SIZE;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxArrow, mxShape);

/**
 * Function: augmentBoundingBox
 *
 * Augments the bounding box with the edge width and markers.
 */
mxArrow.prototype.augmentBoundingBox = function (bbox) {
  mxShape.prototype.augmentBoundingBox.apply(this, arguments);

  var w = Math.max(this.arrowWidth, this.endSize);
  bbox.grow((w / 2 + this.strokewidth) * this.scale);
};

/**
 * Function: paintEdgeShape
 *
 * Paints the line shape.
 */
mxArrow.prototype.paintEdgeShape = function (c, pts) {
  // Geometry of arrow
  var spacing = mxConstants.ARROW_SPACING;
  var width = mxConstants.ARROW_WIDTH;
  var arrow = mxConstants.ARROW_SIZE;

  // Base vector (between end points)
  var p0 = pts[0];
  var pe = pts[pts.length - 1];
  var dx = pe.x - p0.x;
  var dy = pe.y - p0.y;
  var dist = Math.sqrt(dx * dx + dy * dy);
  var length = dist - 2 * spacing - arrow;

  // Computes the norm and the inverse norm
  var nx = dx / dist;
  var ny = dy / dist;
  var basex = length * nx;
  var basey = length * ny;
  var floorx = (width * ny) / 3;
  var floory = (-width * nx) / 3;

  // Computes points
  var p0x = p0.x - floorx / 2 + spacing * nx;
  var p0y = p0.y - floory / 2 + spacing * ny;
  var p1x = p0x + floorx;
  var p1y = p0y + floory;
  var p2x = p1x + basex;
  var p2y = p1y + basey;
  var p3x = p2x + floorx;
  var p3y = p2y + floory;
  // p4 not necessary
  var p5x = p3x - 3 * floorx;
  var p5y = p3y - 3 * floory;

  c.begin();
  c.moveTo(p0x, p0y);
  c.lineTo(p1x, p1y);
  c.lineTo(p2x, p2y);
  c.lineTo(p3x, p3y);
  c.lineTo(pe.x - spacing * nx, pe.y - spacing * ny);
  c.lineTo(p5x, p5y);
  c.lineTo(p5x + floorx, p5y + floory);
  c.close();

  c.fillAndStroke();
};

/**
 * shape/mxArrowConnector.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxArrowConnector
 *
 * Extends <mxShape> to implement an new rounded arrow shape with support for
 * waypoints and double arrows. (The shape is used to represent edges, not
 * vertices.) This shape is registered under <mxConstants.SHAPE_ARROW_CONNECTOR>
 * in <mxCellRenderer>.
 *
 * Constructor: mxArrowConnector
 *
 * Constructs a new arrow shape.
 *
 * Parameters:
 *
 * points - Array of <mxPoints> that define the points. This is stored in
 * <mxShape.points>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 * arrowWidth - Optional integer that defines the arrow width. Default is
 * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
 * spacing - Optional integer that defines the spacing between the arrow shape
 * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
 * <spacing>.
 * endSize - Optional integer that defines the size of the arrowhead. Default
 * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
 */
function mxArrowConnector(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize) {
  mxShape.call(this);
  this.points = points;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
  this.arrowWidth = arrowWidth != null ? arrowWidth : mxConstants.ARROW_WIDTH;
  this.arrowSpacing = spacing != null ? spacing : mxConstants.ARROW_SPACING;
  this.startSize = mxConstants.ARROW_SIZE / 5;
  this.endSize = mxConstants.ARROW_SIZE / 5;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxArrowConnector, mxShape);

/**
 * Variable: useSvgBoundingBox
 *
 * Allows to use the SVG bounding box in SVG. Default is false for performance
 * reasons.
 */
mxArrowConnector.prototype.useSvgBoundingBox = true;

/**
 * Function: isRoundable
 *
 * Hook for subclassers.
 */
mxArrowConnector.prototype.isRoundable = function () {
  return true;
};

/**
 * Variable: resetStyles
 *
 * Overrides mxShape to reset spacing.
 */
mxArrowConnector.prototype.resetStyles = function () {
  mxShape.prototype.resetStyles.apply(this, arguments);

  this.arrowSpacing = mxConstants.ARROW_SPACING;
};

/**
 * Overrides apply to get smooth transition from default start- and endsize.
 */
mxArrowConnector.prototype.apply = function (state) {
  mxShape.prototype.apply.apply(this, arguments);

  if (this.style != null) {
    this.startSize = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) * 3;
    this.endSize = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) * 3;
  }
};

/**
 * Function: augmentBoundingBox
 *
 * Augments the bounding box with the edge width and markers.
 */
mxArrowConnector.prototype.augmentBoundingBox = function (bbox) {
  mxShape.prototype.augmentBoundingBox.apply(this, arguments);

  var w = this.getEdgeWidth();

  if (this.isMarkerStart()) {
    w = Math.max(w, this.getStartArrowWidth());
  }

  if (this.isMarkerEnd()) {
    w = Math.max(w, this.getEndArrowWidth());
  }

  bbox.grow((w / 2 + this.strokewidth) * this.scale);
};

/**
 * Function: paintEdgeShape
 *
 * Paints the line shape.
 */
mxArrowConnector.prototype.paintEdgeShape = function (c, pts) {
  // Geometry of arrow
  var strokeWidth = this.strokewidth;

  if (this.outline) {
    strokeWidth = Math.max(1, mxUtils.getNumber(this.style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth));
  }

  var startWidth = this.getStartArrowWidth() + strokeWidth;
  var endWidth = this.getEndArrowWidth() + strokeWidth;
  var edgeWidth = this.outline ? this.getEdgeWidth() + strokeWidth : this.getEdgeWidth();
  var openEnded = this.isOpenEnded();
  var markerStart = this.isMarkerStart();
  var markerEnd = this.isMarkerEnd();
  var spacing = openEnded ? 0 : this.arrowSpacing + strokeWidth / 2;
  var startSize = this.startSize + strokeWidth;
  var endSize = this.endSize + strokeWidth;
  var isRounded = this.isArrowRounded();

  // Base vector (between first points)
  var pe = pts[pts.length - 1];

  // Finds first non-overlapping point
  var i0 = 1;

  while (i0 < pts.length - 1 && pts[i0].x == pts[0].x && pts[i0].y == pts[0].y) {
    i0++;
  }

  var dx = pts[i0].x - pts[0].x;
  var dy = pts[i0].y - pts[0].y;
  var dist = Math.sqrt(dx * dx + dy * dy);

  if (dist == 0) {
    return;
  }

  // Computes the norm and the inverse norm
  var nx = dx / dist;
  var nx2,
    nx1 = nx;
  var ny = dy / dist;
  var ny2,
    ny1 = ny;
  var orthx = edgeWidth * ny;
  var orthy = -edgeWidth * nx;

  // Stores the inbound function calls in reverse order in fns
  var fns = [];

  if (isRounded) {
    c.setLineJoin('round');
  } else if (pts.length > 2) {
    // Only mitre if there are waypoints
    c.setMiterLimit(1.42);
  }

  c.begin();

  var startNx = nx;
  var startNy = ny;

  if (markerStart && !openEnded) {
    this.paintMarker(c, pts[0].x, pts[0].y, nx, ny, startSize, startWidth, edgeWidth, spacing, true);
  } else {
    var outStartX = pts[0].x + orthx / 2 + spacing * nx;
    var outStartY = pts[0].y + orthy / 2 + spacing * ny;
    var inEndX = pts[0].x - orthx / 2 + spacing * nx;
    var inEndY = pts[0].y - orthy / 2 + spacing * ny;

    if (openEnded) {
      c.moveTo(outStartX, outStartY);

      fns.push(function () {
        c.lineTo(inEndX, inEndY);
      });
    } else {
      c.moveTo(inEndX, inEndY);
      c.lineTo(outStartX, outStartY);
    }
  }

  var dx1 = 0;
  var dy1 = 0;
  var dist1 = 0;

  for (var i = 0; i < pts.length - 2; i++) {
    // Work out in which direction the line is bending
    var pos = mxUtils.relativeCcw(pts[i].x, pts[i].y, pts[i + 1].x, pts[i + 1].y, pts[i + 2].x, pts[i + 2].y);

    dx1 = pts[i + 2].x - pts[i + 1].x;
    dy1 = pts[i + 2].y - pts[i + 1].y;

    dist1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);

    if (dist1 != 0) {
      nx1 = dx1 / dist1;
      ny1 = dy1 / dist1;

      var tmp1 = nx * nx1 + ny * ny1;
      var tmp = Math.max(Math.sqrt((tmp1 + 1) / 2), 0.04);

      // Work out the normal orthogonal to the line through the control point and the edge sides intersection
      nx2 = nx + nx1;
      ny2 = ny + ny1;

      var dist2 = Math.sqrt(nx2 * nx2 + ny2 * ny2);

      if (dist2 != 0) {
        nx2 = nx2 / dist2;
        ny2 = ny2 / dist2;

        // Higher strokewidths require a larger minimum bend, 0.35 covers all but the most extreme cases
        var strokeWidthFactor = Math.max(tmp, Math.min(this.strokewidth / 200 + 0.04, 0.35));
        var angleFactor = pos != 0 && isRounded ? Math.max(0.1, strokeWidthFactor) : Math.max(tmp, 0.06);

        var outX = pts[i + 1].x + (ny2 * edgeWidth) / 2 / angleFactor;
        var outY = pts[i + 1].y - (nx2 * edgeWidth) / 2 / angleFactor;
        var inX = pts[i + 1].x - (ny2 * edgeWidth) / 2 / angleFactor;
        var inY = pts[i + 1].y + (nx2 * edgeWidth) / 2 / angleFactor;

        if (pos == 0 || !isRounded) {
          // If the two segments are aligned, or if we're not drawing curved sections between segments
          // just draw straight to the intersection point
          c.lineTo(outX, outY);

          (function (x, y) {
            fns.push(function () {
              c.lineTo(x, y);
            });
          })(inX, inY);
        } else if (pos == -1) {
          var c1x = inX + ny * edgeWidth;
          var c1y = inY - nx * edgeWidth;
          var c2x = inX + ny1 * edgeWidth;
          var c2y = inY - nx1 * edgeWidth;
          c.lineTo(c1x, c1y);
          c.quadTo(outX, outY, c2x, c2y);

          (function (x, y) {
            fns.push(function () {
              c.lineTo(x, y);
            });
          })(inX, inY);
        } else {
          c.lineTo(outX, outY);

          (function (x, y) {
            var c1x = outX - ny * edgeWidth;
            var c1y = outY + nx * edgeWidth;
            var c2x = outX - ny1 * edgeWidth;
            var c2y = outY + nx1 * edgeWidth;

            fns.push(function () {
              c.quadTo(x, y, c1x, c1y);
            });
            fns.push(function () {
              c.lineTo(c2x, c2y);
            });
          })(inX, inY);
        }

        nx = nx1;
        ny = ny1;
      }
    }
  }

  orthx = edgeWidth * ny1;
  orthy = -edgeWidth * nx1;

  if (markerEnd && !openEnded) {
    this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, false);
  } else {
    c.lineTo(pe.x - spacing * nx1 + orthx / 2, pe.y - spacing * ny1 + orthy / 2);

    var inStartX = pe.x - spacing * nx1 - orthx / 2;
    var inStartY = pe.y - spacing * ny1 - orthy / 2;

    if (!openEnded) {
      c.lineTo(inStartX, inStartY);
    } else {
      c.moveTo(inStartX, inStartY);

      fns.splice(0, 0, function () {
        c.moveTo(inStartX, inStartY);
      });
    }
  }

  for (var i = fns.length - 1; i >= 0; i--) {
    fns[i]();
  }

  if (openEnded) {
    c.end();
    c.stroke();
  } else {
    c.close();
    c.fillAndStroke();
  }

  // Workaround for shadow on top of base arrow
  c.setShadow(false);

  // Need to redraw the markers without the low miter limit
  c.setMiterLimit(4);

  if (isRounded) {
    c.setLineJoin('flat');
  }

  if (pts.length > 2) {
    // Only to repaint markers if no waypoints
    // Need to redraw the markers without the low miter limit
    c.setMiterLimit(4);
    if (markerStart && !openEnded) {
      c.begin();
      this.paintMarker(c, pts[0].x, pts[0].y, startNx, startNy, startSize, startWidth, edgeWidth, spacing, true);
      c.stroke();
      c.end();
    }

    if (markerEnd && !openEnded) {
      c.begin();
      this.paintMarker(c, pe.x, pe.y, -nx, -ny, endSize, endWidth, edgeWidth, spacing, true);
      c.stroke();
      c.end();
    }
  }
};

/**
 * Function: paintMarker
 *
 * Paints the marker.
 */
mxArrowConnector.prototype.paintMarker = function (
  c,
  ptX,
  ptY,
  nx,
  ny,
  size,
  arrowWidth,
  edgeWidth,
  spacing,
  initialMove
) {
  var widthArrowRatio = edgeWidth / arrowWidth;
  var orthx = (edgeWidth * ny) / 2;
  var orthy = (-edgeWidth * nx) / 2;

  var spaceX = (spacing + size) * nx;
  var spaceY = (spacing + size) * ny;

  if (initialMove) {
    c.moveTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
  } else {
    c.lineTo(ptX - orthx + spaceX, ptY - orthy + spaceY);
  }

  c.lineTo(ptX - orthx / widthArrowRatio + spaceX, ptY - orthy / widthArrowRatio + spaceY);
  c.lineTo(ptX + spacing * nx, ptY + spacing * ny);
  c.lineTo(ptX + orthx / widthArrowRatio + spaceX, ptY + orthy / widthArrowRatio + spaceY);
  c.lineTo(ptX + orthx + spaceX, ptY + orthy + spaceY);
};

/**
 * Function: isArrowRounded
 *
 * Returns wether the arrow is rounded
 */
mxArrowConnector.prototype.isArrowRounded = function () {
  return this.isRounded;
};

/**
 * Function: getStartArrowWidth
 *
 * Returns the width of the start arrow
 */
mxArrowConnector.prototype.getStartArrowWidth = function () {
  return mxConstants.ARROW_WIDTH;
};

/**
 * Function: getEndArrowWidth
 *
 * Returns the width of the end arrow
 */
mxArrowConnector.prototype.getEndArrowWidth = function () {
  return mxConstants.ARROW_WIDTH;
};

/**
 * Function: getEdgeWidth
 *
 * Returns the width of the body of the edge
 */
mxArrowConnector.prototype.getEdgeWidth = function () {
  return mxConstants.ARROW_WIDTH / 3;
};

/**
 * Function: isOpenEnded
 *
 * Returns whether the ends of the shape are drawn
 */
mxArrowConnector.prototype.isOpenEnded = function () {
  return false;
};

/**
 * Function: isMarkerStart
 *
 * Returns whether the start marker is drawn
 */
mxArrowConnector.prototype.isMarkerStart = function () {
  return mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE;
};

/**
 * Function: isMarkerEnd
 *
 * Returns whether the end marker is drawn
 */
mxArrowConnector.prototype.isMarkerEnd = function () {
  return mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE;
};

/**
 * shape/mxText.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxText
 *
 * Extends <mxShape> to implement a text shape. To change vertical text from
 * bottom to top to top to bottom, the following code can be used:
 *
 * (code)
 * mxText.prototype.verticalTextRotation = 90;
 * (end)
 *
 * Constructor: mxText
 *
 * Constructs a new text shape.
 *
 * Parameters:
 *
 * value - String that represents the text to be displayed. This is stored in
 * <value>.
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * align - Specifies the horizontal alignment. Default is ''. This is stored in
 * <align>.
 * valign - Specifies the vertical alignment. Default is ''. This is stored in
 * <valign>.
 * color - String that specifies the text color. Default is 'black'. This is
 * stored in <color>.
 * family - String that specifies the font family. Default is
 * <mxConstants.DEFAULT_FONTFAMILY>. This is stored in <family>.
 * size - Integer that specifies the font size. Default is
 * <mxConstants.DEFAULT_FONTSIZE>. This is stored in <size>.
 * fontStyle - Specifies the font style. Default is 0. This is stored in
 * <fontStyle>.
 * spacing - Integer that specifies the global spacing. Default is 2. This is
 * stored in <spacing>.
 * spacingTop - Integer that specifies the top spacing. Default is 0. The
 * sum of the spacing and this is stored in <spacingTop>.
 * spacingRight - Integer that specifies the right spacing. Default is 0. The
 * sum of the spacing and this is stored in <spacingRight>.
 * spacingBottom - Integer that specifies the bottom spacing. Default is 0.The
 * sum of the spacing and this is stored in <spacingBottom>.
 * spacingLeft - Integer that specifies the left spacing. Default is 0. The
 * sum of the spacing and this is stored in <spacingLeft>.
 * horizontal - Boolean that specifies if the label is horizontal. Default is
 * true. This is stored in <horizontal>.
 * background - String that specifies the background color. Default is null.
 * This is stored in <background>.
 * border - String that specifies the label border color. Default is null.
 * This is stored in <border>.
 * wrap - Specifies if word-wrapping should be enabled. Default is false.
 * This is stored in <wrap>.
 * clipped - Specifies if the label should be clipped. Default is false.
 * This is stored in <clipped>.
 * overflow - Value of the overflow style. Default is 'visible'.
 */
function mxText(
  value,
  bounds,
  align,
  valign,
  color,
  family,
  size,
  fontStyle,
  spacing,
  spacingTop,
  spacingRight,
  spacingBottom,
  spacingLeft,
  horizontal,
  background,
  border,
  wrap,
  clipped,
  overflow,
  labelPadding,
  textDirection
) {
  mxShape.call(this);
  this.value = value;
  this.bounds = bounds;
  this.color = color != null ? color : 'black';
  this.align = align != null ? align : mxConstants.ALIGN_CENTER;
  this.valign = valign != null ? valign : mxConstants.ALIGN_MIDDLE;
  this.family = family != null ? family : mxConstants.DEFAULT_FONTFAMILY;
  this.size = size != null ? size : mxConstants.DEFAULT_FONTSIZE;
  this.fontStyle = fontStyle != null ? fontStyle : mxConstants.DEFAULT_FONTSTYLE;
  this.spacing = parseInt(spacing || 2);
  this.spacingTop = this.spacing + parseInt(spacingTop || 0);
  this.spacingRight = this.spacing + parseInt(spacingRight || 0);
  this.spacingBottom = this.spacing + parseInt(spacingBottom || 0);
  this.spacingLeft = this.spacing + parseInt(spacingLeft || 0);
  this.horizontal = horizontal != null ? horizontal : true;
  this.background = background;
  this.border = border;
  this.wrap = wrap != null ? wrap : false;
  this.clipped = clipped != null ? clipped : false;
  this.overflow = overflow != null ? overflow : 'visible';
  this.labelPadding = labelPadding != null ? labelPadding : 0;
  this.textDirection = textDirection;
  this.rotation = 0;
  this.updateMargin();
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxText, mxShape);

/**
 * Variable: baseSpacingTop
 *
 * Specifies the spacing to be added to the top spacing. Default is 0. Use the
 * value 5 here to get the same label positions as in mxGraph 1.x.
 */
mxText.prototype.baseSpacingTop = 0;

/**
 * Variable: baseSpacingBottom
 *
 * Specifies the spacing to be added to the bottom spacing. Default is 0. Use the
 * value 1 here to get the same label positions as in mxGraph 1.x.
 */
mxText.prototype.baseSpacingBottom = 0;

/**
 * Variable: baseSpacingLeft
 *
 * Specifies the spacing to be added to the left spacing. Default is 0.
 */
mxText.prototype.baseSpacingLeft = 0;

/**
 * Variable: baseSpacingRight
 *
 * Specifies the spacing to be added to the right spacing. Default is 0.
 */
mxText.prototype.baseSpacingRight = 0;

/**
 * Variable: replaceLinefeeds
 *
 * Specifies if linefeeds in HTML labels should be replaced with BR tags.
 * Default is true.
 */
mxText.prototype.replaceLinefeeds = true;

/**
 * Variable: verticalTextRotation
 *
 * Rotation for vertical text. Default is -90 (bottom to top).
 */
mxText.prototype.verticalTextRotation = -90;

/**
 * Variable: ignoreClippedStringSize
 *
 * Specifies if the string size should be measured in <updateBoundingBox> if
 * the label is clipped and the label position is center and middle. If this is
 * true, then the bounding box will be set to <bounds>. Default is true.
 * <ignoreStringSize> has precedence over this switch.
 */
mxText.prototype.ignoreClippedStringSize = true;

/**
 * Variable: ignoreStringSize
 *
 * Specifies if the actual string size should be measured. If disabled the
 * boundingBox will not ignore the actual size of the string, otherwise
 * <bounds> will be used instead. Default is false.
 */
mxText.prototype.ignoreStringSize = false;

/**
 * Variable: textWidthPadding
 *
 * Specifies the padding to be added to the text width for the bounding box.
 * This is needed to make sure no clipping is applied to borders. Default is 4
 * for IE 8 standards mode and 3 for all others.
 */
mxText.prototype.textWidthPadding = document.documentMode == 8 && !mxClient.IS_EM ? 4 : 3;

/**
 * Variable: lastValue
 *
 * Contains the last rendered text value. Used for caching.
 */
mxText.prototype.lastValue = null;

/**
 * Variable: cacheEnabled
 *
 * Specifies if caching for HTML labels should be enabled. Default is true.
 */
mxText.prototype.cacheEnabled = true;

/**
 * Function: isParseVml
 *
 * Text shapes do not contain VML markup and do not need to be parsed. This
 * method returns false to speed up rendering in IE8.
 */
mxText.prototype.isParseVml = function () {
  return false;
};

/**
 * Function: isHtmlAllowed
 *
 * Returns true if HTML is allowed for this shape. This implementation returns
 * true if the browser is not in IE8 standards mode.
 */
mxText.prototype.isHtmlAllowed = function () {
  return document.documentMode != 8 || mxClient.IS_EM;
};

/**
 * Function: getSvgScreenOffset
 *
 * Disables offset in IE9 for crisper image output.
 */
mxText.prototype.getSvgScreenOffset = function () {
  return 0;
};

/**
 * Function: checkBounds
 *
 * Returns true if the bounds are not null and all of its variables are numeric.
 */
mxText.prototype.checkBounds = function () {
  return (
    !isNaN(this.scale) &&
    isFinite(this.scale) &&
    this.scale > 0 &&
    this.bounds != null &&
    !isNaN(this.bounds.x) &&
    !isNaN(this.bounds.y) &&
    !isNaN(this.bounds.width) &&
    !isNaN(this.bounds.height)
  );
};

/**
 * Function: paint
 *
 * Generic rendering code.
 */
mxText.prototype.paint = function (c, update) {
  // Scale is passed-through to canvas
  var s = this.scale;
  var x = this.bounds.x / s;
  var y = this.bounds.y / s;
  var w = this.bounds.width / s;
  var h = this.bounds.height / s;

  this.updateTransform(c, x, y, w, h);
  this.configureCanvas(c, x, y, w, h);

  if (update) {
    c.updateText(
      x,
      y,
      w,
      h,
      this.align,
      this.valign,
      this.wrap,
      this.overflow,
      this.clipped,
      this.getTextRotation(),
      this.node
    );
  } else {
    // Checks if text contains HTML markup
    var realHtml = mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML;

    // Always renders labels as HTML in VML
    var fmt = realHtml || c instanceof mxVmlCanvas2D ? 'html' : '';
    var val = this.value;

    if (!realHtml && fmt == 'html') {
      val = mxUtils.htmlEntities(val, false);
    }

    if (fmt == 'html' && !mxUtils.isNode(this.value)) {
      val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');
    }

    // Handles trailing newlines to make sure they are visible in rendering output
    val = !mxUtils.isNode(this.value) && this.replaceLinefeeds && fmt == 'html' ? val.replace(/\n/g, '<br/>') : val;

    var dir = this.textDirection;

    if (dir == mxConstants.TEXT_DIRECTION_AUTO && !realHtml) {
      dir = this.getAutoDirection();
    }

    if (dir != mxConstants.TEXT_DIRECTION_LTR && dir != mxConstants.TEXT_DIRECTION_RTL) {
      dir = null;
    }

    c.text(
      x,
      y,
      w,
      h,
      val,
      this.align,
      this.valign,
      this.wrap,
      fmt,
      this.overflow,
      this.clipped,
      this.getTextRotation(),
      dir
    );
  }
};

/**
 * Function: redraw
 *
 * Renders the text using the given DOM nodes.
 */
mxText.prototype.redraw = function () {
  if (
    this.visible &&
    this.checkBounds() &&
    this.cacheEnabled &&
    this.lastValue == this.value &&
    (mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML)
  ) {
    if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML)) {
      if (mxClient.IS_SVG) {
        this.redrawHtmlShapeWithCss3();
      } else {
        this.updateSize(this.node, this.state == null || this.state.view.textDiv == null);

        if (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8)) {
          this.updateHtmlFilter();
        } else {
          this.updateHtmlTransform();
        }
      }

      this.updateBoundingBox();
    } else {
      var canvas = this.createCanvas();

      if (canvas != null && canvas.updateText != null) {
        // Specifies if events should be handled
        canvas.pointerEvents = this.pointerEvents;

        this.paint(canvas, true);
        this.destroyCanvas(canvas);
        this.updateBoundingBox();
      } else {
        // Fallback if canvas does not support updateText (VML)
        mxShape.prototype.redraw.apply(this, arguments);
      }
    }
  } else {
    mxShape.prototype.redraw.apply(this, arguments);

    if (mxUtils.isNode(this.value) || this.dialect == mxConstants.DIALECT_STRICTHTML) {
      this.lastValue = this.value;
    } else {
      this.lastValue = null;
    }
  }
};

/**
 * Function: resetStyles
 *
 * Resets all styles.
 */
mxText.prototype.resetStyles = function () {
  mxShape.prototype.resetStyles.apply(this, arguments);

  this.color = 'black';
  this.align = mxConstants.ALIGN_CENTER;
  this.valign = mxConstants.ALIGN_MIDDLE;
  this.family = mxConstants.DEFAULT_FONTFAMILY;
  this.size = mxConstants.DEFAULT_FONTSIZE;
  this.fontStyle = mxConstants.DEFAULT_FONTSTYLE;
  this.spacing = 2;
  this.spacingTop = 2;
  this.spacingRight = 2;
  this.spacingBottom = 2;
  this.spacingLeft = 2;
  this.horizontal = true;
  delete this.background;
  delete this.border;
  this.textDirection = mxConstants.DEFAULT_TEXT_DIRECTION;
  delete this.margin;
};

/**
 * Function: apply
 *
 * Extends mxShape to update the text styles.
 *
 * Parameters:
 *
 * state - <mxCellState> of the corresponding cell.
 */
mxText.prototype.apply = function (state) {
  var old = this.spacing;
  mxShape.prototype.apply.apply(this, arguments);

  if (this.style != null) {
    this.fontStyle = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSTYLE, this.fontStyle);
    this.family = mxUtils.getValue(this.style, mxConstants.STYLE_FONTFAMILY, this.family);
    this.size = mxUtils.getValue(this.style, mxConstants.STYLE_FONTSIZE, this.size);
    this.color = mxUtils.getValue(this.style, mxConstants.STYLE_FONTCOLOR, this.color);
    this.align = mxUtils.getValue(this.style, mxConstants.STYLE_ALIGN, this.align);
    this.valign = mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_ALIGN, this.valign);
    this.spacing = parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING, this.spacing));
    this.spacingTop =
      parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_TOP, this.spacingTop - old)) + this.spacing;
    this.spacingRight =
      parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_RIGHT, this.spacingRight - old)) + this.spacing;
    this.spacingBottom =
      parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_BOTTOM, this.spacingBottom - old)) + this.spacing;
    this.spacingLeft =
      parseInt(mxUtils.getValue(this.style, mxConstants.STYLE_SPACING_LEFT, this.spacingLeft - old)) + this.spacing;
    this.horizontal = mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, this.horizontal);
    this.background = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, this.background);
    this.border = mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_BORDERCOLOR, this.border);
    this.textDirection = mxUtils.getValue(
      this.style,
      mxConstants.STYLE_TEXT_DIRECTION,
      mxConstants.DEFAULT_TEXT_DIRECTION
    );
    this.opacity = mxUtils.getValue(this.style, mxConstants.STYLE_TEXT_OPACITY, 100);
    this.updateMargin();
  }

  this.flipV = null;
  this.flipH = null;
};

/**
 * Function: getAutoDirection
 *
 * Used to determine the automatic text direction. Returns
 * <mxConstants.TEXT_DIRECTION_LTR> or <mxConstants.TEXT_DIRECTION_RTL>
 * depending on the contents of <value>. This is not invoked for HTML, wrapped
 * content or if <value> is a DOM node.
 */
mxText.prototype.getAutoDirection = function () {
  // Looks for strong (directional) characters
  var tmp = /[A-Za-z\u05d0-\u065f\u066a-\u06ef\u06fa-\u07ff\ufb1d-\ufdff\ufe70-\ufefc]/.exec(this.value);

  // Returns the direction defined by the character
  return tmp != null && tmp.length > 0 && tmp[0] > 'z'
    ? mxConstants.TEXT_DIRECTION_RTL
    : mxConstants.TEXT_DIRECTION_LTR;
};

/**
 * Function: getContentNode
 *
 * Returns the node that contains the rendered input.
 */
mxText.prototype.getContentNode = function () {
  var result = this.node;

  if (result != null) {
    // Rendered with no foreignObject
    if (result.ownerSVGElement == null) {
      result = this.node.firstChild.firstChild;
    } else {
      // Innermost DIV that contains the actual content
      result = result.firstChild.firstChild.firstChild.firstChild.firstChild;
    }
  }

  return result;
};

/**
 * Function: updateBoundingBox
 *
 * Updates the <boundingBox> for this shape using the given node and position.
 */
mxText.prototype.updateBoundingBox = function () {
  var node = this.node;
  this.boundingBox = this.bounds.clone();
  var rot = this.getTextRotation();

  var h =
    this.style != null
      ? mxUtils.getValue(this.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER)
      : null;
  var v =
    this.style != null
      ? mxUtils.getValue(this.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE)
      : null;

  if (
    !this.ignoreStringSize &&
    node != null &&
    this.overflow != 'fill' &&
    (!this.clipped || !this.ignoreClippedStringSize || h != mxConstants.ALIGN_CENTER || v != mxConstants.ALIGN_MIDDLE)
  ) {
    var ow = null;
    var oh = null;

    if (node.ownerSVGElement != null) {
      if (
        node.firstChild != null &&
        node.firstChild.firstChild != null &&
        node.firstChild.firstChild.nodeName == 'foreignObject'
      ) {
        // Uses second inner DIV for font metrics
        node = node.firstChild.firstChild.firstChild.firstChild;
        oh = node.offsetHeight * this.scale;

        if (this.overflow == 'width') {
          ow = this.boundingBox.width;
        } else {
          ow = node.offsetWidth * this.scale;
        }
      } else {
        try {
          var b = node.getBBox();

          // Workaround for bounding box of empty string
          if (typeof this.value == 'string' && mxUtils.trim(this.value) == 0) {
            this.boundingBox = null;
          } else if (b.width == 0 && b.height == 0) {
            this.boundingBox = null;
          } else {
            this.boundingBox = new mxRectangle(b.x, b.y, b.width, b.height);
          }

          return;
        } catch (e) {
          // Ignores NS_ERROR_FAILURE in FF if container display is none.
        }
      }
    } else {
      var td = this.state != null ? this.state.view.textDiv : null;

      // Use cached offset size
      if (this.offsetWidth != null && this.offsetHeight != null) {
        ow = this.offsetWidth * this.scale;
        oh = this.offsetHeight * this.scale;
      } else {
        // Cannot get node size while container hidden so a
        // shared temporary DIV is used for text measuring
        if (td != null) {
          this.updateFont(td);
          this.updateSize(td, false);
          this.updateInnerHtml(td);

          node = td;
        }

        var sizeDiv = node;

        if (document.documentMode == 8 && !mxClient.IS_EM) {
          var w = Math.round(this.bounds.width / this.scale);

          if (this.wrap && w > 0) {
            node.style.wordWrap = mxConstants.WORD_WRAP;
            node.style.whiteSpace = 'normal';

            if (node.style.wordWrap != 'break-word') {
              // Innermost DIV is used for measuring text
              var divs = sizeDiv.getElementsByTagName('div');

              if (divs.length > 0) {
                sizeDiv = divs[divs.length - 1];
              }

              ow = sizeDiv.offsetWidth + 2;
              divs = this.node.getElementsByTagName('div');

              if (this.clipped) {
                ow = Math.min(w, ow);
              }

              // Second last DIV width must be updated in DOM tree
              if (divs.length > 1) {
                divs[divs.length - 2].style.width = ow + 'px';
              }
            }
          } else {
            node.style.whiteSpace = 'nowrap';
          }
        } else if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') {
          sizeDiv = sizeDiv.firstChild;
        }

        this.offsetWidth = sizeDiv.offsetWidth + this.textWidthPadding;
        this.offsetHeight = sizeDiv.offsetHeight;

        ow = this.offsetWidth * this.scale;
        oh = this.offsetHeight * this.scale;
      }
    }

    if (ow != null && oh != null) {
      this.boundingBox = new mxRectangle(this.bounds.x, this.bounds.y, ow, oh);
    }
  }

  if (this.boundingBox != null) {
    if (rot != 0) {
      // Accounts for pre-rotated x and y
      var bbox = mxUtils.getBoundingBox(
        new mxRectangle(
          this.margin.x * this.boundingBox.width,
          this.margin.y * this.boundingBox.height,
          this.boundingBox.width,
          this.boundingBox.height
        ),
        rot,
        new mxPoint(0, 0)
      );

      this.unrotatedBoundingBox = mxRectangle.fromRectangle(this.boundingBox);
      this.unrotatedBoundingBox.x += this.margin.x * this.unrotatedBoundingBox.width;
      this.unrotatedBoundingBox.y += this.margin.y * this.unrotatedBoundingBox.height;

      this.boundingBox.x += bbox.x;
      this.boundingBox.y += bbox.y;
      this.boundingBox.width = bbox.width;
      this.boundingBox.height = bbox.height;
    } else {
      this.boundingBox.x += this.margin.x * this.boundingBox.width;
      this.boundingBox.y += this.margin.y * this.boundingBox.height;
      this.unrotatedBoundingBox = null;
    }
  }
};

/**
 * Function: getShapeRotation
 *
 * Returns 0 to avoid using rotation in the canvas via updateTransform.
 */
mxText.prototype.getShapeRotation = function () {
  return 0;
};

/**
 * Function: getTextRotation
 *
 * Returns the rotation for the text label of the corresponding shape.
 */
mxText.prototype.getTextRotation = function () {
  return this.state != null && this.state.shape != null ? this.state.shape.getTextRotation() : 0;
};

/**
 * Function: isPaintBoundsInverted
 *
 * Inverts the bounds if <mxShape.isBoundsInverted> returns true or if the
 * horizontal style is false.
 */
mxText.prototype.isPaintBoundsInverted = function () {
  return !this.horizontal && this.state != null && this.state.view.graph.model.isVertex(this.state.cell);
};

/**
 * Function: configureCanvas
 *
 * Sets the state of the canvas for drawing the shape.
 */
mxText.prototype.configureCanvas = function (c, x, y, w, h) {
  mxShape.prototype.configureCanvas.apply(this, arguments);

  c.setFontColor(this.color);
  c.setFontBackgroundColor(this.background);
  c.setFontBorderColor(this.border);
  c.setFontFamily(this.family);
  c.setFontSize(this.size);
  c.setFontStyle(this.fontStyle);
};

/**
 * Function: updateVmlContainer
 *
 * Sets the width and height of the container to 1px.
 */
mxText.prototype.updateVmlContainer = function () {
  this.node.style.left = Math.round(this.bounds.x) + 'px';
  this.node.style.top = Math.round(this.bounds.y) + 'px';
  this.node.style.width = '1px';
  this.node.style.height = '1px';
  this.node.style.overflow = 'visible';
};

/**
 * Function: getHtmlValue
 *
 * Private helper function to create SVG elements
 */
mxText.prototype.getHtmlValue = function () {
  var val = this.value;

  if (this.dialect != mxConstants.DIALECT_STRICTHTML) {
    val = mxUtils.htmlEntities(val, false);
  }

  // Handles trailing newlines to make sure they are visible in rendering output
  val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');
  val = this.replaceLinefeeds ? val.replace(/\n/g, '<br/>') : val;

  return val;
};

/**
 * Function: getTextCss
 *
 * Private helper function to create SVG elements
 */
mxText.prototype.getTextCss = function () {
  var lh = mxConstants.ABSOLUTE_LINE_HEIGHT ? this.size * mxConstants.LINE_HEIGHT + 'px' : mxConstants.LINE_HEIGHT;

  var css =
    'display: inline-block; font-size: ' +
    this.size +
    'px; ' +
    'font-family: ' +
    this.family +
    '; color: ' +
    this.color +
    '; line-height: ' +
    lh +
    '; pointer-events: ' +
    (this.pointerEvents ? 'all' : 'none') +
    '; ';

  if ((this.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
    css += 'font-weight: bold; ';
  }

  if ((this.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
    css += 'font-style: italic; ';
  }

  var deco = [];

  if ((this.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
    deco.push('underline');
  }

  if ((this.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) {
    deco.push('line-through');
  }

  if (deco.length > 0) {
    css += 'text-decoration: ' + deco.join(' ') + '; ';
  }

  return css;
};

/**
 * Function: redrawHtmlShape
 *
 * Updates the HTML node(s) to reflect the latest bounds and scale.
 */
mxText.prototype.redrawHtmlShape = function () {
  if (mxClient.IS_SVG) {
    this.redrawHtmlShapeWithCss3();
  } else {
    var style = this.node.style;

    // Resets CSS styles
    style.whiteSpace = 'normal';
    style.overflow = '';
    style.width = '';
    style.height = '';

    this.updateValue();
    this.updateFont(this.node);
    this.updateSize(this.node, this.state == null || this.state.view.textDiv == null);

    this.offsetWidth = null;
    this.offsetHeight = null;

    if (mxClient.IS_IE && (document.documentMode == null || document.documentMode <= 8)) {
      this.updateHtmlFilter();
    } else {
      this.updateHtmlTransform();
    }
  }
};

/**
 * Function: redrawHtmlShapeWithCss3
 *
 * Updates the HTML node(s) to reflect the latest bounds and scale.
 */
mxText.prototype.redrawHtmlShapeWithCss3 = function () {
  var w = Math.max(0, Math.round(this.bounds.width / this.scale));
  var h = Math.max(0, Math.round(this.bounds.height / this.scale));
  var flex =
    'position: absolute; left: ' +
    Math.round(this.bounds.x) +
    'px; ' +
    'top: ' +
    Math.round(this.bounds.y) +
    'px; pointer-events: none; ';
  var block = this.getTextCss();

  mxSvgCanvas2D.createCss(
    w + 2,
    h,
    this.align,
    this.valign,
    this.wrap,
    this.overflow,
    this.clipped,
    this.background != null ? mxUtils.htmlEntities(this.background) : null,
    this.border != null ? mxUtils.htmlEntities(this.border) : null,
    flex,
    block,
    this.scale,
    mxUtils.bind(this, function (dx, dy, flex, item, block, ofl) {
      var r = this.getTextRotation();
      var tr =
        (this.scale != 1 ? 'scale(' + this.scale + ') ' : '') +
        (r != 0 ? 'rotate(' + r + 'deg) ' : '') +
        (this.margin.x != 0 || this.margin.y != 0
          ? 'translate(' + this.margin.x * 100 + '%,' + this.margin.y * 100 + '%)'
          : '');

      if (tr != '') {
        tr = 'transform-origin: 0 0; transform: ' + tr + '; ';
      }

      if (ofl == '') {
        flex += item;
        item = 'display:inline-block; min-width: 100%; ' + tr;
      } else {
        item += tr;

        if (mxClient.IS_SF) {
          item += '-webkit-clip-path: content-box;';
        }
      }

      if (this.opacity < 100) {
        block += 'opacity: ' + this.opacity / 100 + '; ';
      }

      this.node.setAttribute('style', flex);

      var html = mxUtils.isNode(this.value) ? this.value.outerHTML : this.getHtmlValue();

      if (this.node.firstChild == null) {
        this.node.innerHTML = '<div><div>' + html + '</div></div>';
      }

      this.node.firstChild.firstChild.setAttribute('style', block);
      this.node.firstChild.setAttribute('style', item);
    })
  );
};

/**
 * Function: updateHtmlTransform
 *
 * Returns the spacing as an <mxPoint>.
 */
mxText.prototype.updateHtmlTransform = function () {
  var theta = this.getTextRotation();
  var style = this.node.style;
  var dx = this.margin.x;
  var dy = this.margin.y;

  if (theta != 0) {
    mxUtils.setPrefixedStyle(style, 'transformOrigin', -dx * 100 + '%' + ' ' + -dy * 100 + '%');
    mxUtils.setPrefixedStyle(
      style,
      'transform',
      'translate(' + dx * 100 + '%' + ',' + dy * 100 + '%) ' + 'scale(' + this.scale + ') rotate(' + theta + 'deg)'
    );
  } else {
    mxUtils.setPrefixedStyle(style, 'transformOrigin', '0% 0%');
    mxUtils.setPrefixedStyle(
      style,
      'transform',
      'scale(' + this.scale + ') ' + 'translate(' + dx * 100 + '%' + ',' + dy * 100 + '%)'
    );
  }

  style.left =
    Math.round(this.bounds.x - Math.ceil(dx * (this.overflow != 'fill' && this.overflow != 'width' ? 3 : 1))) + 'px';
  style.top = Math.round(this.bounds.y - dy * (this.overflow != 'fill' ? 3 : 1)) + 'px';

  if (this.opacity < 100) {
    style.opacity = this.opacity / 100;
  } else {
    style.opacity = '';
  }
};

/**
 * Function: updateInnerHtml
 *
 * Sets the inner HTML of the given element to the <value>.
 */
mxText.prototype.updateInnerHtml = function (elt) {
  if (mxUtils.isNode(this.value)) {
    elt.innerHTML = this.value.outerHTML;
  } else {
    var val = this.value;

    if (this.dialect != mxConstants.DIALECT_STRICTHTML) {
      // LATER: Can be cached in updateValue
      val = mxUtils.htmlEntities(val, false);
    }

    // Handles trailing newlines to make sure they are visible in rendering output
    val = mxUtils.replaceTrailingNewlines(val, '<div>&nbsp;</div>');
    val = this.replaceLinefeeds ? val.replace(/\n/g, '<br/>') : val;
    val = '<div style="display:inline-block;_display:inline;">' + val + '</div>';

    elt.innerHTML = val;
  }
};

/**
 * Function: updateHtmlFilter
 *
 * Rotated text rendering quality is bad for IE9 quirks/IE8 standards
 */
mxText.prototype.updateHtmlFilter = function () {
  var style = this.node.style;
  var dx = this.margin.x;
  var dy = this.margin.y;
  var s = this.scale;

  // Resets filter before getting offsetWidth
  mxUtils.setOpacity(this.node, this.opacity);

  // Adds 1 to match table height in 1.x
  var ow = 0;
  var oh = 0;
  var td = this.state != null ? this.state.view.textDiv : null;
  var sizeDiv = this.node;

  // Fallback for hidden text rendering in IE quirks mode
  if (td != null) {
    td.style.overflow = '';
    td.style.height = '';
    td.style.width = '';

    this.updateFont(td);
    this.updateSize(td, false);
    this.updateInnerHtml(td);

    var w = Math.round(this.bounds.width / this.scale);

    if (this.wrap && w > 0) {
      td.style.whiteSpace = 'normal';
      td.style.wordWrap = mxConstants.WORD_WRAP;
      ow = w;

      if (this.clipped) {
        ow = Math.min(ow, this.bounds.width);
      }

      td.style.width = ow + 'px';
    } else {
      td.style.whiteSpace = 'nowrap';
    }

    sizeDiv = td;

    if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') {
      sizeDiv = sizeDiv.firstChild;

      if (this.wrap && td.style.wordWrap == 'break-word') {
        sizeDiv.style.width = '100%';
      }
    }

    // Required to update the height of the text box after wrapping width is known
    if (!this.clipped && this.wrap && w > 0) {
      ow = sizeDiv.offsetWidth + this.textWidthPadding;
      td.style.width = ow + 'px';
    }

    oh = sizeDiv.offsetHeight + 2;

    if (mxClient.IS_QUIRKS && this.border != null && this.border != mxConstants.NONE) {
      oh += 3;
    }
  } else if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') {
    sizeDiv = sizeDiv.firstChild;
    oh = sizeDiv.offsetHeight;
  }

  ow = sizeDiv.offsetWidth + this.textWidthPadding;

  if (this.clipped) {
    oh = Math.min(oh, this.bounds.height);
  }

  var w = this.bounds.width / s;
  var h = this.bounds.height / s;

  // Handles special case for live preview with no wrapper DIV and no textDiv
  if (this.overflow == 'fill') {
    oh = h;
    ow = w;
  } else if (this.overflow == 'width') {
    oh = sizeDiv.scrollHeight;
    ow = w;
  }

  // Stores for later use
  this.offsetWidth = ow;
  this.offsetHeight = oh;

  // Simulates max-height CSS in quirks mode
  if (mxClient.IS_QUIRKS && (this.clipped || (this.overflow == 'width' && h > 0))) {
    h = Math.min(h, oh);
    style.height = Math.round(h) + 'px';
  } else {
    h = oh;
  }

  if (this.overflow != 'fill' && this.overflow != 'width') {
    if (this.clipped) {
      ow = Math.min(w, ow);
    }

    w = ow;

    // Simulates max-width CSS in quirks mode
    if ((mxClient.IS_QUIRKS && this.clipped) || this.wrap) {
      style.width = Math.round(w) + 'px';
    }
  }

  h *= s;
  w *= s;

  // Rotation case is handled via VML canvas
  var rad = this.getTextRotation() * (Math.PI / 180);

  // Precalculate cos and sin for the rotation
  var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8));
  var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8));

  rad %= 2 * Math.PI;

  if (rad < 0) {
    rad += 2 * Math.PI;
  }

  rad %= Math.PI;

  if (rad > Math.PI / 2) {
    rad = Math.PI - rad;
  }

  var cos = Math.cos(rad);
  var sin = Math.sin(-rad);

  var tx = w * -(dx + 0.5);
  var ty = h * -(dy + 0.5);

  var top_fix = (h - h * cos + w * sin) / 2 + real_sin * tx - real_cos * ty;
  var left_fix = (w - w * cos + h * sin) / 2 - real_cos * tx - real_sin * ty;

  if (rad != 0) {
    var f =
      'progid:DXImageTransform.Microsoft.Matrix(M11=' +
      real_cos +
      ', M12=' +
      real_sin +
      ', M21=' +
      -real_sin +
      ', M22=' +
      real_cos +
      ", sizingMethod='auto expand')";

    if (style.filter != null && style.filter.length > 0) {
      style.filter += ' ' + f;
    } else {
      style.filter = f;
    }
  }

  // Workaround for rendering offsets
  var dy = 0;

  if (this.overflow != 'fill' && mxClient.IS_QUIRKS) {
    if (this.valign == mxConstants.ALIGN_TOP) {
      dy -= 1;
    } else if (this.valign == mxConstants.ALIGN_BOTTOM) {
      dy += 2;
    } else {
      dy += 1;
    }
  }

  style.zoom = s;
  style.left = Math.round(this.bounds.x + left_fix - w / 2) + 'px';
  style.top = Math.round(this.bounds.y + top_fix - h / 2 + dy) + 'px';
};

/**
 * Function: updateValue
 *
 * Updates the HTML node(s) to reflect the latest bounds and scale.
 */
mxText.prototype.updateValue = function () {
  if (mxUtils.isNode(this.value)) {
    this.node.innerHTML = '';
    this.node.appendChild(this.value);
  } else {
    var val = this.value;

    if (this.dialect != mxConstants.DIALECT_STRICTHTML) {
      val = mxUtils.htmlEntities(val, false);
    }

    // Handles trailing newlines to make sure they are visible in rendering output
    val = mxUtils.replaceTrailingNewlines(val, '<div><br></div>');
    val = this.replaceLinefeeds ? val.replace(/\n/g, '<br/>') : val;
    var bg = this.background != null && this.background != mxConstants.NONE ? this.background : null;
    var bd = this.border != null && this.border != mxConstants.NONE ? this.border : null;

    if (this.overflow == 'fill' || this.overflow == 'width') {
      if (bg != null) {
        this.node.style.backgroundColor = bg;
      }

      if (bd != null) {
        this.node.style.border = '1px solid ' + bd;
      }
    } else {
      var css = '';

      if (bg != null) {
        css += 'background-color:' + mxUtils.htmlEntities(bg) + ';';
      }

      if (bd != null) {
        css += 'border:1px solid ' + mxUtils.htmlEntities(bd) + ';';
      }

      // Wrapper DIV for background, zoom needed for inline in quirks
      // and to measure wrapped font sizes in all browsers
      // FIXME: Background size in quirks mode for wrapped text
      var lh = mxConstants.ABSOLUTE_LINE_HEIGHT ? this.size * mxConstants.LINE_HEIGHT + 'px' : mxConstants.LINE_HEIGHT;
      val =
        '<div style="zoom:1;' +
        css +
        'display:inline-block;_display:inline;text-decoration:inherit;' +
        'padding-bottom:1px;padding-right:1px;line-height:' +
        lh +
        '">' +
        val +
        '</div>';
    }

    this.node.innerHTML = val;

    // Sets text direction
    var divs = this.node.getElementsByTagName('div');

    if (divs.length > 0) {
      var dir = this.textDirection;

      if (dir == mxConstants.TEXT_DIRECTION_AUTO && this.dialect != mxConstants.DIALECT_STRICTHTML) {
        dir = this.getAutoDirection();
      }

      if (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL) {
        divs[divs.length - 1].setAttribute('dir', dir);
      } else {
        divs[divs.length - 1].removeAttribute('dir');
      }
    }
  }
};

/**
 * Function: updateFont
 *
 * Updates the HTML node(s) to reflect the latest bounds and scale.
 */
mxText.prototype.updateFont = function (node) {
  var style = node.style;

  style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT
    ? this.size * mxConstants.LINE_HEIGHT + 'px'
    : mxConstants.LINE_HEIGHT;
  style.fontSize = this.size + 'px';
  style.fontFamily = this.family;
  style.verticalAlign = 'top';
  style.color = this.color;

  if ((this.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
    style.fontWeight = 'bold';
  } else {
    style.fontWeight = '';
  }

  if ((this.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
    style.fontStyle = 'italic';
  } else {
    style.fontStyle = '';
  }

  var txtDecor = [];

  if ((this.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
    txtDecor.push('underline');
  }

  if ((this.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) {
    txtDecor.push('line-through');
  }

  style.textDecoration = txtDecor.join(' ');

  if (this.align == mxConstants.ALIGN_CENTER) {
    style.textAlign = 'center';
  } else if (this.align == mxConstants.ALIGN_RIGHT) {
    style.textAlign = 'right';
  } else {
    style.textAlign = 'left';
  }
};

/**
 * Function: updateSize
 *
 * Updates the HTML node(s) to reflect the latest bounds and scale.
 */
mxText.prototype.updateSize = function (node, enableWrap) {
  var w = Math.max(0, Math.round(this.bounds.width / this.scale));
  var h = Math.max(0, Math.round(this.bounds.height / this.scale));
  var style = node.style;

  // NOTE: Do not use maxWidth here because wrapping will
  // go wrong if the cell is outside of the viewable area
  if (this.clipped) {
    style.overflow = 'hidden';

    if (!mxClient.IS_QUIRKS) {
      style.maxHeight = h + 'px';
      style.maxWidth = w + 'px';
    } else {
      style.width = w + 'px';
    }
  } else if (this.overflow == 'fill') {
    style.width = w + 1 + 'px';
    style.height = h + 1 + 'px';
    style.overflow = 'hidden';
  } else if (this.overflow == 'width') {
    style.width = w + 1 + 'px';
    style.maxHeight = h + 1 + 'px';
    style.overflow = 'hidden';
  }

  if (this.wrap && w > 0) {
    style.wordWrap = mxConstants.WORD_WRAP;
    style.whiteSpace = 'normal';
    style.width = w + 'px';

    if (enableWrap && this.overflow != 'fill' && this.overflow != 'width') {
      var sizeDiv = node;

      if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') {
        sizeDiv = sizeDiv.firstChild;

        if (node.style.wordWrap == 'break-word') {
          sizeDiv.style.width = '100%';
        }
      }

      var tmp = sizeDiv.offsetWidth;

      // Workaround for text measuring in hidden containers
      if (tmp == 0) {
        var prev = node.parentNode;
        node.style.visibility = 'hidden';
        document.body.appendChild(node);
        tmp = sizeDiv.offsetWidth;
        node.style.visibility = '';
        prev.appendChild(node);
      }

      tmp += 3;

      if (this.clipped) {
        tmp = Math.min(tmp, w);
      }

      style.width = tmp + 'px';
    }
  } else {
    style.whiteSpace = 'nowrap';
  }
};

/**
 * Function: getMargin
 *
 * Returns the spacing as an <mxPoint>.
 */
mxText.prototype.updateMargin = function () {
  this.margin = mxUtils.getAlignmentAsPoint(this.align, this.valign);
};

/**
 * Function: getSpacing
 *
 * Returns the spacing as an <mxPoint>.
 */
mxText.prototype.getSpacing = function () {
  var dx = 0;
  var dy = 0;

  if (this.align == mxConstants.ALIGN_CENTER) {
    dx = (this.spacingLeft - this.spacingRight) / 2;
  } else if (this.align == mxConstants.ALIGN_RIGHT) {
    dx = -this.spacingRight - this.baseSpacingRight;
  } else {
    dx = this.spacingLeft + this.baseSpacingLeft;
  }

  if (this.valign == mxConstants.ALIGN_MIDDLE) {
    dy = (this.spacingTop - this.spacingBottom) / 2;
  } else if (this.valign == mxConstants.ALIGN_BOTTOM) {
    dy = -this.spacingBottom - this.baseSpacingBottom;
  } else {
    dy = this.spacingTop + this.baseSpacingTop;
  }

  return new mxPoint(dx, dy);
};

/**
 * shape/mxTriangle.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxTriangle
 *
 * Implementation of the triangle shape.
 *
 * Constructor: mxTriangle
 *
 * Constructs a new triangle shape.
 */
function mxTriangle() {
  mxActor.call(this);
}

/**
 * Extends mxActor.
 */
mxUtils.extend(mxTriangle, mxActor);

/**
 * Function: isRoundable
 *
 * Adds roundable support.
 */
mxTriangle.prototype.isRoundable = function () {
  return true;
};

/**
 * Function: redrawPath
 *
 * Draws the path for this shape.
 */
mxTriangle.prototype.redrawPath = function (c, x, y, w, h) {
  var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
  this.addPoints(c, [new mxPoint(0, 0), new mxPoint(w, 0.5 * h), new mxPoint(0, h)], this.isRounded, arcSize, true);
};

/**
 * shape/mxHexagon.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxHexagon
 *
 * Implementation of the hexagon shape.
 *
 * Constructor: mxHexagon
 *
 * Constructs a new hexagon shape.
 */
function mxHexagon() {
  mxActor.call(this);
}

/**
 * Extends mxActor.
 */
mxUtils.extend(mxHexagon, mxActor);

/**
 * Function: redrawPath
 *
 * Draws the path for this shape.
 */
mxHexagon.prototype.redrawPath = function (c, x, y, w, h) {
  var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
  this.addPoints(
    c,
    [
      new mxPoint(0.25 * w, 0),
      new mxPoint(0.75 * w, 0),
      new mxPoint(w, 0.5 * h),
      new mxPoint(0.75 * w, h),
      new mxPoint(0.25 * w, h),
      new mxPoint(0, 0.5 * h),
    ],
    this.isRounded,
    arcSize,
    true
  );
};

/**
 * shape/mxLine.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxLine
 *
 * Extends <mxShape> to implement a horizontal line shape.
 * This shape is registered under <mxConstants.SHAPE_LINE> in
 * <mxCellRenderer>.
 *
 * Constructor: mxLine
 *
 * Constructs a new line shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * stroke - String that defines the stroke color. Default is 'black'. This is
 * stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxLine(bounds, stroke, strokewidth, vertical) {
  mxShape.call(this);
  this.bounds = bounds;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
  this.vertical = vertical != null ? vertical : this.vertical;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxLine, mxShape);

/**
 * Function: vertical
 *
 * Whether to paint a vertical line.
 */
mxLine.prototype.vertical = false;

/**
 * Function: paintVertexShape
 *
 * Redirects to redrawPath for subclasses to work.
 */
mxLine.prototype.paintVertexShape = function (c, x, y, w, h) {
  c.begin();

  if (this.vertical) {
    var mid = x + w / 2;
    c.moveTo(mid, y);
    c.lineTo(mid, y + h);
  } else {
    var mid = y + h / 2;
    c.moveTo(x, mid);
    c.lineTo(x + w, mid);
  }

  c.stroke();
};

/**
 * shape/mxImageShape.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxImageShape
 *
 * Extends <mxShape> to implement an image shape. This shape is registered
 * under <mxConstants.SHAPE_IMAGE> in <mxCellRenderer>.
 *
 * Constructor: mxImageShape
 *
 * Constructs a new image shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * image - String that specifies the URL of the image. This is stored in
 * <image>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 0. This is stored in <strokewidth>.
 */
function mxImageShape(bounds, image, fill, stroke, strokewidth) {
  mxShape.call(this);
  this.bounds = bounds;
  this.image = image;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
  this.shadow = false;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxImageShape, mxRectangleShape);

/**
 * Variable: preserveImageAspect
 *
 * Switch to preserve image aspect. Default is true.
 */
mxImageShape.prototype.preserveImageAspect = true;

/**
 * Function: getSvgScreenOffset
 *
 * Disables offset in IE9 for crisper image output.
 */
mxImageShape.prototype.getSvgScreenOffset = function () {
  return 0;
};

/**
 * Function: apply
 *
 * Overrides <mxShape.apply> to replace the fill and stroke colors with the
 * respective values from <mxConstants.STYLE_IMAGE_BACKGROUND> and
 * <mxConstants.STYLE_IMAGE_BORDER>.
 *
 * Applies the style of the given <mxCellState> to the shape. This
 * implementation assigns the following styles to local fields:
 *
 * - <mxConstants.STYLE_IMAGE_BACKGROUND> => fill
 * - <mxConstants.STYLE_IMAGE_BORDER> => stroke
 *
 * Parameters:
 *
 * state - <mxCellState> of the corresponding cell.
 */
mxImageShape.prototype.apply = function (state) {
  mxShape.prototype.apply.apply(this, arguments);

  this.fill = null;
  this.stroke = null;
  this.gradient = null;

  if (this.style != null) {
    this.preserveImageAspect = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1;

    // Legacy support for imageFlipH/V
    this.flipH = this.flipH || mxUtils.getValue(this.style, 'imageFlipH', 0) == 1;
    this.flipV = this.flipV || mxUtils.getValue(this.style, 'imageFlipV', 0) == 1;
  }
};

/**
 * Function: isHtmlAllowed
 *
 * Returns true if HTML is allowed for this shape. This implementation always
 * returns false.
 */
mxImageShape.prototype.isHtmlAllowed = function () {
  return !this.preserveImageAspect;
};

/**
 * Function: createHtml
 *
 * Creates and returns the HTML DOM node(s) to represent
 * this shape. This implementation falls back to <createVml>
 * so that the HTML creation is optional.
 */
mxImageShape.prototype.createHtml = function () {
  var node = document.createElement('div');
  node.style.position = 'absolute';

  return node;
};

/**
 * Function: isRoundable
 *
 * Disables inherited roundable support.
 */
mxImageShape.prototype.isRoundable = function (c, x, y, w, h) {
  return false;
};

/**
 * Function: paintVertexShape
 *
 * Generic background painting implementation.
 */
mxImageShape.prototype.paintVertexShape = function (c, x, y, w, h) {
  if (this.image != null) {
    var fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, null);
    var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);

    if (fill != null) {
      // Stroke rendering required for shadow
      c.setFillColor(fill);
      c.setStrokeColor(stroke);
      c.rect(x, y, w, h);
      c.fillAndStroke();
    }

    // FlipH/V are implicit via mxShape.updateTransform
    c.image(x, y, w, h, this.image, this.preserveImageAspect, false, false);

    var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, null);

    if (stroke != null) {
      c.setShadow(false);
      c.setStrokeColor(stroke);
      c.rect(x, y, w, h);
      c.stroke();
    }
  } else {
    mxRectangleShape.prototype.paintBackground.apply(this, arguments);
  }
};

/**
 * Function: redraw
 *
 * Overrides <mxShape.redraw> to preserve the aspect ratio of images.
 */
mxImageShape.prototype.redrawHtmlShape = function () {
  this.node.style.left = Math.round(this.bounds.x) + 'px';
  this.node.style.top = Math.round(this.bounds.y) + 'px';
  this.node.style.width = Math.max(0, Math.round(this.bounds.width)) + 'px';
  this.node.style.height = Math.max(0, Math.round(this.bounds.height)) + 'px';
  this.node.innerHTML = '';

  if (this.image != null) {
    var fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND, '');
    var stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER, '');
    this.node.style.backgroundColor = fill;
    this.node.style.borderColor = stroke;

    // VML image supports PNG in IE6
    var useVml =
      mxClient.IS_IE6 || ((document.documentMode == null || document.documentMode <= 8) && this.rotation != 0);
    var img = document.createElement(useVml ? mxClient.VML_PREFIX + ':image' : 'img');
    img.setAttribute('border', '0');
    img.style.position = 'absolute';
    img.src = this.image;

    var filter = this.opacity < 100 ? 'alpha(opacity=' + this.opacity + ')' : '';
    this.node.style.filter = filter;

    if (this.flipH && this.flipV) {
      filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
    } else if (this.flipH) {
      filter += 'progid:DXImageTransform.Microsoft.BasicImage(mirror=1)';
    } else if (this.flipV) {
      filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
    }

    if (img.style.filter != filter) {
      img.style.filter = filter;
    }

    if (img.nodeName == 'image') {
      img.style.rotation = this.rotation;
    } else if (this.rotation != 0) {
      // LATER: Add flipV/H support
      mxUtils.setPrefixedStyle(img.style, 'transform', 'rotate(' + this.rotation + 'deg)');
    } else {
      mxUtils.setPrefixedStyle(img.style, 'transform', '');
    }

    // Known problem: IE clips top line of image for certain angles
    img.style.width = this.node.style.width;
    img.style.height = this.node.style.height;

    this.node.style.backgroundImage = '';
    this.node.appendChild(img);
  } else {
    this.setTransparentBackgroundImage(this.node);
  }
};

/**
 * shape/mxLabel.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxLabel
 *
 * Extends <mxShape> to implement an image shape with a label.
 * This shape is registered under <mxConstants.SHAPE_LABEL> in
 * <mxCellRenderer>.
 *
 * Constructor: mxLabel
 *
 * Constructs a new label shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxLabel(bounds, fill, stroke, strokewidth) {
  mxRectangleShape.call(this, bounds, fill, stroke, strokewidth);
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxLabel, mxRectangleShape);

/**
 * Variable: imageSize
 *
 * Default width and height for the image. Default is
 * <mxConstants.DEFAULT_IMAGESIZE>.
 */
mxLabel.prototype.imageSize = mxConstants.DEFAULT_IMAGESIZE;

/**
 * Variable: spacing
 *
 * Default value for image spacing. Default is 2.
 */
mxLabel.prototype.spacing = 2;

/**
 * Variable: indicatorSize
 *
 * Default width and height for the indicicator. Default is 10.
 */
mxLabel.prototype.indicatorSize = 10;

/**
 * Variable: indicatorSpacing
 *
 * Default spacing between image and indicator. Default is 2.
 */
mxLabel.prototype.indicatorSpacing = 2;

/**
 * Function: init
 *
 * Initializes the shape and the <indicator>.
 */
mxLabel.prototype.init = function (container) {
  mxShape.prototype.init.apply(this, arguments);

  if (this.indicatorShape != null) {
    this.indicator = new this.indicatorShape();
    this.indicator.dialect = this.dialect;
    this.indicator.init(this.node);
  }
};

/**
 * Function: redraw
 *
 * Reconfigures this shape. This will update the colors of the indicator
 * and reconfigure it if required.
 */
mxLabel.prototype.redraw = function () {
  if (this.indicator != null) {
    this.indicator.fill = this.indicatorColor;
    this.indicator.stroke = this.indicatorStrokeColor;
    this.indicator.gradient = this.indicatorGradientColor;
    this.indicator.direction = this.indicatorDirection;
    this.indicator.redraw();
  }

  mxShape.prototype.redraw.apply(this, arguments);
};

/**
 * Function: isHtmlAllowed
 *
 * Returns true for non-rounded, non-rotated shapes with no glass gradient and
 * no indicator shape.
 */
mxLabel.prototype.isHtmlAllowed = function () {
  return (
    mxRectangleShape.prototype.isHtmlAllowed.apply(this, arguments) &&
    this.indicatorColor == null &&
    this.indicatorShape == null
  );
};

/**
 * Function: paintForeground
 *
 * Generic background painting implementation.
 */
mxLabel.prototype.paintForeground = function (c, x, y, w, h) {
  this.paintImage(c, x, y, w, h);
  this.paintIndicator(c, x, y, w, h);

  mxRectangleShape.prototype.paintForeground.apply(this, arguments);
};

/**
 * Function: paintImage
 *
 * Generic background painting implementation.
 */
mxLabel.prototype.paintImage = function (c, x, y, w, h) {
  if (this.image != null) {
    var bounds = this.getImageBounds(x, y, w, h);
    c.image(bounds.x, bounds.y, bounds.width, bounds.height, this.image, false, false, false);
  }
};

/**
 * Function: getImageBounds
 *
 * Generic background painting implementation.
 */
mxLabel.prototype.getImageBounds = function (x, y, w, h) {
  var align = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);
  var valign = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
  var width = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_WIDTH, mxConstants.DEFAULT_IMAGESIZE);
  var height = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_HEIGHT, mxConstants.DEFAULT_IMAGESIZE);
  var spacing = mxUtils.getNumber(this.style, mxConstants.STYLE_SPACING, this.spacing) + 5;

  if (align == mxConstants.ALIGN_CENTER) {
    x += (w - width) / 2;
  } else if (align == mxConstants.ALIGN_RIGHT) {
    x += w - width - spacing;
  } // default is left
  else {
    x += spacing;
  }

  if (valign == mxConstants.ALIGN_TOP) {
    y += spacing;
  } else if (valign == mxConstants.ALIGN_BOTTOM) {
    y += h - height - spacing;
  } // default is middle
  else {
    y += (h - height) / 2;
  }

  return new mxRectangle(x, y, width, height);
};

/**
 * Function: paintIndicator
 *
 * Generic background painting implementation.
 */
mxLabel.prototype.paintIndicator = function (c, x, y, w, h) {
  if (this.indicator != null) {
    this.indicator.bounds = this.getIndicatorBounds(x, y, w, h);
    this.indicator.paint(c);
  } else if (this.indicatorImage != null) {
    var bounds = this.getIndicatorBounds(x, y, w, h);
    c.image(bounds.x, bounds.y, bounds.width, bounds.height, this.indicatorImage, false, false, false);
  }
};

/**
 * Function: getIndicatorBounds
 *
 * Generic background painting implementation.
 */
mxLabel.prototype.getIndicatorBounds = function (x, y, w, h) {
  var align = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);
  var valign = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
  var width = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_WIDTH, this.indicatorSize);
  var height = mxUtils.getNumber(this.style, mxConstants.STYLE_INDICATOR_HEIGHT, this.indicatorSize);
  var spacing = this.spacing + 5;

  if (align == mxConstants.ALIGN_RIGHT) {
    x += w - width - spacing;
  } else if (align == mxConstants.ALIGN_CENTER) {
    x += (w - width) / 2;
  } // default is left
  else {
    x += spacing;
  }

  if (valign == mxConstants.ALIGN_BOTTOM) {
    y += h - height - spacing;
  } else if (valign == mxConstants.ALIGN_TOP) {
    y += spacing;
  } // default is middle
  else {
    y += (h - height) / 2;
  }

  return new mxRectangle(x, y, width, height);
};
/**
 * Function: redrawHtmlShape
 *
 * Generic background painting implementation.
 */
mxLabel.prototype.redrawHtmlShape = function () {
  mxRectangleShape.prototype.redrawHtmlShape.apply(this, arguments);

  // Removes all children
  while (this.node.hasChildNodes()) {
    this.node.removeChild(this.node.lastChild);
  }

  if (this.image != null) {
    var node = document.createElement('img');
    node.style.position = 'relative';
    node.setAttribute('border', '0');

    var bounds = this.getImageBounds(this.bounds.x, this.bounds.y, this.bounds.width, this.bounds.height);
    bounds.x -= this.bounds.x;
    bounds.y -= this.bounds.y;

    node.style.left = Math.round(bounds.x) + 'px';
    node.style.top = Math.round(bounds.y) + 'px';
    node.style.width = Math.round(bounds.width) + 'px';
    node.style.height = Math.round(bounds.height) + 'px';

    node.src = this.image;

    this.node.appendChild(node);
  }
};

/**
 * shape/mxCylinder.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCylinder
 *
 * Extends <mxShape> to implement an cylinder shape. If a
 * custom shape with one filled area and an overlay path is
 * needed, then this shape's <redrawPath> should be overridden.
 * This shape is registered under <mxConstants.SHAPE_CYLINDER>
 * in <mxCellRenderer>.
 *
 * Constructor: mxCylinder
 *
 * Constructs a new cylinder shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxCylinder(bounds, fill, stroke, strokewidth) {
  mxShape.call(this);
  this.bounds = bounds;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxCylinder, mxShape);

/**
 * Variable: maxHeight
 *
 * Defines the maximum height of the top and bottom part
 * of the cylinder shape.
 */
mxCylinder.prototype.maxHeight = 40;

/**
 * Variable: svgStrokeTolerance
 *
 * Sets stroke tolerance to 0 for SVG.
 */
mxCylinder.prototype.svgStrokeTolerance = 0;

/**
 * Function: paintVertexShape
 *
 * Redirects to redrawPath for subclasses to work.
 */
mxCylinder.prototype.paintVertexShape = function (c, x, y, w, h) {
  c.translate(x, y);
  c.begin();
  this.redrawPath(c, x, y, w, h, false);
  c.fillAndStroke();

  if (
    !this.outline ||
    this.style == null ||
    mxUtils.getValue(this.style, mxConstants.STYLE_BACKGROUND_OUTLINE, 0) == 0
  ) {
    c.setShadow(false);
    c.begin();
    this.redrawPath(c, x, y, w, h, true);
    c.stroke();
  }
};

/**
 * Function: getCylinderSize
 *
 * Returns the cylinder size.
 */
mxCylinder.prototype.getCylinderSize = function (x, y, w, h) {
  return Math.min(this.maxHeight, Math.round(h / 5));
};

/**
 * Function: redrawPath
 *
 * Draws the path for this shape.
 */
mxCylinder.prototype.redrawPath = function (c, x, y, w, h, isForeground) {
  var dy = this.getCylinderSize(x, y, w, h);

  if ((isForeground && this.fill != null) || (!isForeground && this.fill == null)) {
    c.moveTo(0, dy);
    c.curveTo(0, 2 * dy, w, 2 * dy, w, dy);

    // Needs separate shapes for correct hit-detection
    if (!isForeground) {
      c.stroke();
      c.begin();
    }
  }

  if (!isForeground) {
    c.moveTo(0, dy);
    c.curveTo(0, -dy / 3, w, -dy / 3, w, dy);
    c.lineTo(w, h - dy);
    c.curveTo(w, h + dy / 3, 0, h + dy / 3, 0, h - dy);
    c.close();
  }
};

/**
 * shape/mxConnector.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxConnector
 *
 * Extends <mxShape> to implement a connector shape. The connector
 * shape allows for arrow heads on either side.
 *
 * This shape is registered under <mxConstants.SHAPE_CONNECTOR> in
 * <mxCellRenderer>.
 *
 * Constructor: mxConnector
 *
 * Constructs a new connector shape.
 *
 * Parameters:
 *
 * points - Array of <mxPoints> that define the points. This is stored in
 * <mxShape.points>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * Default is 'black'.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxConnector(points, stroke, strokewidth) {
  mxPolyline.call(this, points, stroke, strokewidth);
}

/**
 * Extends mxPolyline.
 */
mxUtils.extend(mxConnector, mxPolyline);

/**
 * Function: updateBoundingBox
 *
 * Updates the <boundingBox> for this shape using <createBoundingBox> and
 * <augmentBoundingBox> and stores the result in <boundingBox>.
 */
mxConnector.prototype.updateBoundingBox = function () {
  this.useSvgBoundingBox = this.style != null && this.style[mxConstants.STYLE_CURVED] == 1;
  mxShape.prototype.updateBoundingBox.apply(this, arguments);
};

/**
 * Function: paintEdgeShape
 *
 * Paints the line shape.
 */
mxConnector.prototype.paintEdgeShape = function (c, pts) {
  // The indirection via functions for markers is needed in
  // order to apply the offsets before painting the line and
  // paint the markers after painting the line.
  var sourceMarker = this.createMarker(c, pts, true);
  var targetMarker = this.createMarker(c, pts, false);

  mxPolyline.prototype.paintEdgeShape.apply(this, arguments);

  // Disables shadows, dashed styles and fixes fill color for markers
  c.setFillColor(this.stroke);
  c.setShadow(false);
  c.setDashed(false);

  if (sourceMarker != null) {
    sourceMarker();
  }

  if (targetMarker != null) {
    targetMarker();
  }
};

/**
 * Function: createMarker
 *
 * Prepares the marker by adding offsets in pts and returning a function to
 * paint the marker.
 */
mxConnector.prototype.createMarker = function (c, pts, source) {
  var result = null;
  var n = pts.length;
  var type = mxUtils.getValue(this.style, source ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW);
  var p0 = source ? pts[1] : pts[n - 2];
  var pe = source ? pts[0] : pts[n - 1];

  if (type != null && p0 != null && pe != null) {
    var count = 1;

    // Uses next non-overlapping point
    while (count < n - 1 && Math.round(p0.x - pe.x) == 0 && Math.round(p0.y - pe.y) == 0) {
      p0 = source ? pts[1 + count] : pts[n - 2 - count];
      count++;
    }

    // Computes the norm and the inverse norm
    var dx = pe.x - p0.x;
    var dy = pe.y - p0.y;

    var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));

    var unitX = dx / dist;
    var unitY = dy / dist;

    var size = mxUtils.getNumber(
      this.style,
      source ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE,
      mxConstants.DEFAULT_MARKERSIZE
    );

    // Allow for stroke width in the end point used and the
    // orthogonal vectors describing the direction of the marker
    var filled = this.style[source ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL] != 0;

    result = mxMarker.createMarker(c, this, type, pe, unitX, unitY, size, source, this.strokewidth, filled);
  }

  return result;
};

/**
 * Function: augmentBoundingBox
 *
 * Augments the bounding box with the strokewidth and shadow offsets.
 */
mxConnector.prototype.augmentBoundingBox = function (bbox) {
  mxShape.prototype.augmentBoundingBox.apply(this, arguments);

  // Adds marker sizes
  var size = 0;

  if (mxUtils.getValue(this.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE) {
    size = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE) + 1;
  }

  if (mxUtils.getValue(this.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE) {
    size = Math.max(size, mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE)) + 1;
  }

  bbox.grow(size * this.scale);
};

/**
 * shape/mxSwimlane.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxSwimlane
 *
 * Extends <mxShape> to implement a swimlane shape. This shape is registered
 * under <mxConstants.SHAPE_SWIMLANE> in <mxCellRenderer>. Use the
 * <mxConstants.STYLE_STYLE_STARTSIZE> to define the size of the title
 * region, <mxConstants.STYLE_SWIMLANE_FILLCOLOR> for the content area fill,
 * <mxConstants.STYLE_SEPARATORCOLOR> to draw an additional vertical separator
 * and <mxConstants.STYLE_SWIMLANE_LINE> to hide the line between the title
 * region and the content area. The <mxConstants.STYLE_HORIZONTAL> affects
 * the orientation of this shape, not only its label.
 *
 * Constructor: mxSwimlane
 *
 * Constructs a new swimlane shape.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that defines the bounds. This is stored in
 * <mxShape.bounds>.
 * fill - String that defines the fill color. This is stored in <fill>.
 * stroke - String that defines the stroke color. This is stored in <stroke>.
 * strokewidth - Optional integer that defines the stroke width. Default is
 * 1. This is stored in <strokewidth>.
 */
function mxSwimlane(bounds, fill, stroke, strokewidth) {
  mxShape.call(this);
  this.bounds = bounds;
  this.fill = fill;
  this.stroke = stroke;
  this.strokewidth = strokewidth != null ? strokewidth : 1;
}

/**
 * Extends mxShape.
 */
mxUtils.extend(mxSwimlane, mxShape);

/**
 * Variable: imageSize
 *
 * Default imagewidth and imageheight if an image but no imagewidth
 * and imageheight are defined in the style. Value is 16.
 */
mxSwimlane.prototype.imageSize = 16;

/**
 * Function: isRoundable
 *
 * Adds roundable support.
 */
mxSwimlane.prototype.isRoundable = function (c, x, y, w, h) {
  return true;
};

/**
 * Function: getTitleSize
 *
 * Returns the title size.
 */
mxSwimlane.prototype.getTitleSize = function () {
  return Math.max(0, mxUtils.getValue(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
};

/**
 * Function: getLabelBounds
 *
 * Returns the bounding box for the label.
 */
mxSwimlane.prototype.getLabelBounds = function (rect) {
  var start = this.getTitleSize();
  var bounds = new mxRectangle(rect.x, rect.y, rect.width, rect.height);
  var horizontal = this.isHorizontal();

  var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, 0) == 1;
  var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, 0) == 1;

  // East is default
  var shapeVertical = this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH;
  var realHorizontal = horizontal == !shapeVertical;

  var realFlipH =
    !realHorizontal &&
    flipH != (this.direction == mxConstants.DIRECTION_SOUTH || this.direction == mxConstants.DIRECTION_WEST);
  var realFlipV =
    realHorizontal &&
    flipV != (this.direction == mxConstants.DIRECTION_SOUTH || this.direction == mxConstants.DIRECTION_WEST);

  // Shape is horizontal
  if (!shapeVertical) {
    var tmp = Math.min(bounds.height, start * this.scale);

    if (realFlipH || realFlipV) {
      bounds.y += bounds.height - tmp;
    }

    bounds.height = tmp;
  } else {
    var tmp = Math.min(bounds.width, start * this.scale);

    if (realFlipH || realFlipV) {
      bounds.x += bounds.width - tmp;
    }

    bounds.width = tmp;
  }

  return bounds;
};

/**
 * Function: getGradientBounds
 *
 * Returns the bounding box for the gradient box for this shape.
 */
mxSwimlane.prototype.getGradientBounds = function (c, x, y, w, h) {
  var start = this.getTitleSize();

  if (this.isHorizontal()) {
    start = Math.min(start, h);
    return new mxRectangle(x, y, w, start);
  } else {
    start = Math.min(start, w);
    return new mxRectangle(x, y, start, h);
  }
};

/**
 * Function: getSwimlaneArcSize
 *
 * Returns the arcsize for the swimlane.
 */
mxSwimlane.prototype.getSwimlaneArcSize = function (w, h, start) {
  if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') {
    return Math.min(
      w / 2,
      Math.min(h / 2, mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2)
    );
  } else {
    var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;

    return start * f * 3;
  }
};

/**
 * Function: isHorizontal
 *
 * Paints the swimlane vertex shape.
 */
mxSwimlane.prototype.isHorizontal = function () {
  return mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
};

/**
 * Function: paintVertexShape
 *
 * Paints the swimlane vertex shape.
 */
mxSwimlane.prototype.paintVertexShape = function (c, x, y, w, h) {
  var start = this.getTitleSize();
  var fill = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_FILLCOLOR, mxConstants.NONE);
  var swimlaneLine = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_LINE, 1) == 1;
  var r = 0;

  if (this.isHorizontal()) {
    start = Math.min(start, h);
  } else {
    start = Math.min(start, w);
  }

  c.translate(x, y);

  if (!this.isRounded) {
    this.paintSwimlane(c, x, y, w, h, start, fill, swimlaneLine);
  } else {
    r = this.getSwimlaneArcSize(w, h, start);
    r = Math.min((this.isHorizontal() ? h : w) - start, Math.min(start, r));
    this.paintRoundedSwimlane(c, x, y, w, h, start, r, fill, swimlaneLine);
  }

  var sep = mxUtils.getValue(this.style, mxConstants.STYLE_SEPARATORCOLOR, mxConstants.NONE);
  this.paintSeparator(c, x, y, w, h, start, sep);

  if (this.image != null) {
    var bounds = this.getImageBounds(x, y, w, h);
    c.image(bounds.x - x, bounds.y - y, bounds.width, bounds.height, this.image, false, false, false);
  }

  if (this.glass) {
    c.setShadow(false);
    this.paintGlassEffect(c, 0, 0, w, start, r);
  }
};

/**
 * Function: paintSwimlane
 *
 * Paints the swimlane vertex shape.
 */
mxSwimlane.prototype.paintSwimlane = function (c, x, y, w, h, start, fill, swimlaneLine) {
  c.begin();

  var events = true;

  if (this.style != null) {
    events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';
  }

  if (!events && (this.fill == null || this.fill == mxConstants.NONE)) {
    c.pointerEvents = false;
  }

  if (this.isHorizontal()) {
    c.moveTo(0, start);
    c.lineTo(0, 0);
    c.lineTo(w, 0);
    c.lineTo(w, start);
    c.fillAndStroke();

    if (start < h) {
      if (fill == mxConstants.NONE || !events) {
        c.pointerEvents = false;
      }

      if (fill != mxConstants.NONE) {
        c.setFillColor(fill);
      }

      c.begin();
      c.moveTo(0, start);
      c.lineTo(0, h);
      c.lineTo(w, h);
      c.lineTo(w, start);

      if (fill == mxConstants.NONE) {
        c.stroke();
      } else {
        c.fillAndStroke();
      }
    }
  } else {
    c.moveTo(start, 0);
    c.lineTo(0, 0);
    c.lineTo(0, h);
    c.lineTo(start, h);
    c.fillAndStroke();

    if (start < w) {
      if (fill == mxConstants.NONE || !events) {
        c.pointerEvents = false;
      }

      if (fill != mxConstants.NONE) {
        c.setFillColor(fill);
      }

      c.begin();
      c.moveTo(start, 0);
      c.lineTo(w, 0);
      c.lineTo(w, h);
      c.lineTo(start, h);

      if (fill == mxConstants.NONE) {
        c.stroke();
      } else {
        c.fillAndStroke();
      }
    }
  }

  if (swimlaneLine) {
    this.paintDivider(c, x, y, w, h, start, fill == mxConstants.NONE);
  }
};

/**
 * Function: paintRoundedSwimlane
 *
 * Paints the swimlane vertex shape.
 */
mxSwimlane.prototype.paintRoundedSwimlane = function (c, x, y, w, h, start, r, fill, swimlaneLine) {
  c.begin();

  var events = true;

  if (this.style != null) {
    events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';
  }

  if (!events && (this.fill == null || this.fill == mxConstants.NONE)) {
    c.pointerEvents = false;
  }

  if (this.isHorizontal()) {
    c.moveTo(w, start);
    c.lineTo(w, r);
    c.quadTo(w, 0, w - Math.min(w / 2, r), 0);
    c.lineTo(Math.min(w / 2, r), 0);
    c.quadTo(0, 0, 0, r);
    c.lineTo(0, start);
    c.fillAndStroke();

    if (start < h) {
      if (fill == mxConstants.NONE || !events) {
        c.pointerEvents = false;
      }

      if (fill != mxConstants.NONE) {
        c.setFillColor(fill);
      }

      c.begin();
      c.moveTo(0, start);
      c.lineTo(0, h - r);
      c.quadTo(0, h, Math.min(w / 2, r), h);
      c.lineTo(w - Math.min(w / 2, r), h);
      c.quadTo(w, h, w, h - r);
      c.lineTo(w, start);

      if (fill == mxConstants.NONE) {
        c.stroke();
      } else {
        c.fillAndStroke();
      }
    }
  } else {
    c.moveTo(start, 0);
    c.lineTo(r, 0);
    c.quadTo(0, 0, 0, Math.min(h / 2, r));
    c.lineTo(0, h - Math.min(h / 2, r));
    c.quadTo(0, h, r, h);
    c.lineTo(start, h);
    c.fillAndStroke();

    if (start < w) {
      if (fill == mxConstants.NONE || !events) {
        c.pointerEvents = false;
      }

      if (fill != mxConstants.NONE) {
        c.setFillColor(fill);
      }

      c.begin();
      c.moveTo(start, h);
      c.lineTo(w - r, h);
      c.quadTo(w, h, w, h - Math.min(h / 2, r));
      c.lineTo(w, Math.min(h / 2, r));
      c.quadTo(w, 0, w - r, 0);
      c.lineTo(start, 0);

      if (fill == mxConstants.NONE) {
        c.stroke();
      } else {
        c.fillAndStroke();
      }
    }
  }

  if (swimlaneLine) {
    this.paintDivider(c, x, y, w, h, start, fill == mxConstants.NONE);
  }
};

/**
 * Function: paintDivider
 *
 * Paints the divider between swimlane title and content area.
 */
mxSwimlane.prototype.paintDivider = function (c, x, y, w, h, start, shadow) {
  if (!shadow) {
    c.setShadow(false);
  }

  c.begin();

  if (this.isHorizontal()) {
    c.moveTo(0, start);
    c.lineTo(w, start);
  } else {
    c.moveTo(start, 0);
    c.lineTo(start, h);
  }

  c.stroke();
};

/**
 * Function: paintSeparator
 *
 * Paints the vertical or horizontal separator line between swimlanes.
 */
mxSwimlane.prototype.paintSeparator = function (c, x, y, w, h, start, color) {
  if (color != mxConstants.NONE) {
    c.setStrokeColor(color);
    c.setDashed(true);
    c.begin();

    if (this.isHorizontal()) {
      c.moveTo(w, start);
      c.lineTo(w, h);
    } else {
      c.moveTo(start, 0);
      c.lineTo(w, 0);
    }

    c.stroke();
    c.setDashed(false);
  }
};

/**
 * Function: getImageBounds
 *
 * Paints the swimlane vertex shape.
 */
mxSwimlane.prototype.getImageBounds = function (x, y, w, h) {
  if (this.isHorizontal()) {
    return new mxRectangle(x + w - this.imageSize, y, this.imageSize, this.imageSize);
  } else {
    return new mxRectangle(x, y, this.imageSize, this.imageSize);
  }
};

/**
 * layout/mxGraphLayout.js
 */
/**
 * Copyright (c) 2006-2018, JGraph Ltd
 * Copyright (c) 2006-2018, Gaudenz Alder
 */
/**
 * Class: mxGraphLayout
 *
 * Base class for all layout algorithms in mxGraph. Main public functions are
 * <moveCell> for handling a moved cell within a layouted parent, and <execute> for
 * running the layout on a given parent cell.
 *
 * Known Subclasses:
 *
 * <mxCircleLayout>, <mxCompactTreeLayout>, <mxCompositeLayout>,
 * <mxFastOrganicLayout>, <mxParallelEdgeLayout>, <mxPartitionLayout>,
 * <mxStackLayout>
 *
 * Constructor: mxGraphLayout
 *
 * Constructs a new layout using the given layouts.
 *
 * Arguments:
 *
 * graph - Enclosing
 */
function mxGraphLayout(graph) {
  this.graph = graph;
}

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxGraphLayout.prototype.graph = null;

/**
 * Variable: useBoundingBox
 *
 * Boolean indicating if the bounding box of the label should be used if
 * its available. Default is true.
 */
mxGraphLayout.prototype.useBoundingBox = true;

/**
 * Variable: parent
 *
 * The parent cell of the layout, if any
 */
mxGraphLayout.prototype.parent = null;

/**
 * Function: moveCell
 *
 * Notified when a cell is being moved in a parent that has automatic
 * layout to update the cell state (eg. index) so that the outcome of the
 * layout will position the vertex as close to the point (x, y) as
 * possible.
 *
 * Empty implementation.
 *
 * Parameters:
 *
 * cell - <mxCell> which has been moved.
 * x - X-coordinate of the new cell location.
 * y - Y-coordinate of the new cell location.
 */
mxGraphLayout.prototype.moveCell = function (cell, x, y) {};

/**
 * Function: resizeCell
 *
 * Notified when a cell is being resized in a parent that has automatic
 * layout to update the other cells in the layout.
 *
 * Empty implementation.
 *
 * Parameters:
 *
 * cell - <mxCell> which has been moved.
 * bounds - <mxRectangle> that represents the new cell bounds.
 */
mxGraphLayout.prototype.resizeCell = function (cell, bounds) {};

/**
 * Function: execute
 *
 * Executes the layout algorithm for the children of the given parent.
 *
 * Parameters:
 *
 * parent - <mxCell> whose children should be layed out.
 */
mxGraphLayout.prototype.execute = function (parent) {};

/**
 * Function: getGraph
 *
 * Returns the graph that this layout operates on.
 */
mxGraphLayout.prototype.getGraph = function () {
  return this.graph;
};

/**
 * Function: getConstraint
 *
 * Returns the constraint for the given key and cell. The optional edge and
 * source arguments are used to return inbound and outgoing routing-
 * constraints for the given edge and vertex. This implementation always
 * returns the value for the given key in the style of the given cell.
 *
 * Parameters:
 *
 * key - Key of the constraint to be returned.
 * cell - <mxCell> whose constraint should be returned.
 * edge - Optional <mxCell> that represents the connection whose constraint
 * should be returned. Default is null.
 * source - Optional boolean that specifies if the connection is incoming
 * or outgoing. Default is null.
 */
mxGraphLayout.prototype.getConstraint = function (key, cell, edge, source) {
  return this.graph.getCurrentCellStyle(cell)[key];
};

/**
 * Function: traverse
 *
 * Traverses the (directed) graph invoking the given function for each
 * visited vertex and edge. The function is invoked with the current vertex
 * and the incoming edge as a parameter. This implementation makes sure
 * each vertex is only visited once. The function may return false if the
 * traversal should stop at the given vertex.
 *
 * Example:
 *
 * (code)
 * mxLog.show();
 * var cell = graph.getSelectionCell();
 * graph.traverse(cell, false, function(vertex, edge)
 * {
 *   mxLog.debug(graph.getLabel(vertex));
 * });
 * (end)
 *
 * Parameters:
 *
 * vertex - <mxCell> that represents the vertex where the traversal starts.
 * directed - Optional boolean indicating if edges should only be traversed
 * from source to target. Default is true.
 * func - Visitor function that takes the current vertex and the incoming
 * edge as arguments. The traversal stops if the function returns false.
 * edge - Optional <mxCell> that represents the incoming edge. This is
 * null for the first step of the traversal.
 * visited - Optional <mxDictionary> of cell paths for the visited cells.
 */
mxGraphLayout.traverse = function (vertex, directed, func, edge, visited) {
  if (func != null && vertex != null) {
    directed = directed != null ? directed : true;
    visited = visited || new mxDictionary();

    if (!visited.get(vertex)) {
      visited.put(vertex, true);
      var result = func(vertex, edge);

      if (result == null || result) {
        var edgeCount = this.graph.model.getEdgeCount(vertex);

        if (edgeCount > 0) {
          for (var i = 0; i < edgeCount; i++) {
            var e = this.graph.model.getEdgeAt(vertex, i);
            var isSource = this.graph.model.getTerminal(e, true) == vertex;

            if (!directed || isSource) {
              var next = this.graph.view.getVisibleTerminal(e, !isSource);
              this.traverse(next, directed, func, e, visited);
            }
          }
        }
      }
    }
  }
};

/**
 * Function: isAncestor
 *
 * Returns true if the given parent is an ancestor of the given child.
 *
 * Parameters:
 *
 * parent - <mxCell> that specifies the parent.
 * child - <mxCell> that specifies the child.
 * traverseAncestors - boolean whether to
 */
mxGraphLayout.prototype.isAncestor = function (parent, child, traverseAncestors) {
  if (!traverseAncestors) {
    return this.graph.model.getParent(child) == parent;
  }

  if (child == parent) {
    return false;
  }

  while (child != null && child != parent) {
    child = this.graph.model.getParent(child);
  }

  return child == parent;
};

/**
 * Function: isVertexMovable
 *
 * Returns a boolean indicating if the given <mxCell> is movable or
 * bendable by the algorithm. This implementation returns true if the given
 * cell is movable in the graph.
 *
 * Parameters:
 *
 * cell - <mxCell> whose movable state should be returned.
 */
mxGraphLayout.prototype.isVertexMovable = function (cell) {
  return this.graph.isCellMovable(cell);
};

/**
 * Function: isVertexIgnored
 *
 * Returns a boolean indicating if the given <mxCell> should be ignored by
 * the algorithm. This implementation returns false for all vertices.
 *
 * Parameters:
 *
 * vertex - <mxCell> whose ignored state should be returned.
 */
mxGraphLayout.prototype.isVertexIgnored = function (vertex) {
  return !this.graph.getModel().isVertex(vertex) || !this.graph.isCellVisible(vertex);
};

/**
 * Function: isEdgeIgnored
 *
 * Returns a boolean indicating if the given <mxCell> should be ignored by
 * the algorithm. This implementation returns false for all vertices.
 *
 * Parameters:
 *
 * cell - <mxCell> whose ignored state should be returned.
 */
mxGraphLayout.prototype.isEdgeIgnored = function (edge) {
  var model = this.graph.getModel();

  return (
    !model.isEdge(edge) ||
    !this.graph.isCellVisible(edge) ||
    model.getTerminal(edge, true) == null ||
    model.getTerminal(edge, false) == null
  );
};

/**
 * Function: setEdgeStyleEnabled
 *
 * Disables or enables the edge style of the given edge.
 */
mxGraphLayout.prototype.setEdgeStyleEnabled = function (edge, value) {
  this.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE, value ? '0' : '1', [edge]);
};

/**
 * Function: setOrthogonalEdge
 *
 * Disables or enables orthogonal end segments of the given edge.
 */
mxGraphLayout.prototype.setOrthogonalEdge = function (edge, value) {
  this.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL, value ? '1' : '0', [edge]);
};

/**
 * Function: getParentOffset
 *
 * Determines the offset of the given parent to the parent
 * of the layout
 */
mxGraphLayout.prototype.getParentOffset = function (parent) {
  var result = new mxPoint();

  if (parent != null && parent != this.parent) {
    var model = this.graph.getModel();

    if (model.isAncestor(this.parent, parent)) {
      var parentGeo = model.getGeometry(parent);

      while (parent != this.parent) {
        result.x = result.x + parentGeo.x;
        result.y = result.y + parentGeo.y;

        parent = model.getParent(parent);
        parentGeo = model.getGeometry(parent);
      }
    }
  }

  return result;
};

/**
 * Function: setEdgePoints
 *
 * Replaces the array of mxPoints in the geometry of the given edge
 * with the given array of mxPoints.
 */
mxGraphLayout.prototype.setEdgePoints = function (edge, points) {
  if (edge != null) {
    var model = this.graph.model;
    var geometry = model.getGeometry(edge);

    if (geometry == null) {
      geometry = new mxGeometry();
      geometry.setRelative(true);
    } else {
      geometry = geometry.clone();
    }

    if (this.parent != null && points != null) {
      var parent = model.getParent(edge);

      var parentOffset = this.getParentOffset(parent);

      for (var i = 0; i < points.length; i++) {
        points[i].x = points[i].x - parentOffset.x;
        points[i].y = points[i].y - parentOffset.y;
      }
    }

    geometry.points = points;
    model.setGeometry(edge, geometry);
  }
};

/**
 * Function: setVertexLocation
 *
 * Sets the new position of the given cell taking into account the size of
 * the bounding box if <useBoundingBox> is true. The change is only carried
 * out if the new location is not equal to the existing location, otherwise
 * the geometry is not replaced with an updated instance. The new or old
 * bounds are returned (including overlapping labels).
 *
 * Parameters:
 *
 * cell - <mxCell> whose geometry is to be set.
 * x - Integer that defines the x-coordinate of the new location.
 * y - Integer that defines the y-coordinate of the new location.
 */
mxGraphLayout.prototype.setVertexLocation = function (cell, x, y) {
  var model = this.graph.getModel();
  var geometry = model.getGeometry(cell);
  var result = null;

  if (geometry != null) {
    result = new mxRectangle(x, y, geometry.width, geometry.height);

    // Checks for oversize labels and shifts the result
    // TODO: Use mxUtils.getStringSize for label bounds
    if (this.useBoundingBox) {
      var state = this.graph.getView().getState(cell);

      if (state != null && state.text != null && state.text.boundingBox != null) {
        var scale = this.graph.getView().scale;
        var box = state.text.boundingBox;

        if (state.text.boundingBox.x < state.x) {
          x += (state.x - box.x) / scale;
          result.width = box.width;
        }

        if (state.text.boundingBox.y < state.y) {
          y += (state.y - box.y) / scale;
          result.height = box.height;
        }
      }
    }

    if (this.parent != null) {
      var parent = model.getParent(cell);

      if (parent != null && parent != this.parent) {
        var parentOffset = this.getParentOffset(parent);

        x = x - parentOffset.x;
        y = y - parentOffset.y;
      }
    }

    if (geometry.x != x || geometry.y != y) {
      geometry = geometry.clone();
      geometry.x = x;
      geometry.y = y;

      model.setGeometry(cell, geometry);
    }
  }

  return result;
};

/**
 * Function: getVertexBounds
 *
 * Returns an <mxRectangle> that defines the bounds of the given cell or
 * the bounding box if <useBoundingBox> is true.
 */
mxGraphLayout.prototype.getVertexBounds = function (cell) {
  var geo = this.graph.getModel().getGeometry(cell);

  // Checks for oversize label bounding box and corrects
  // the return value accordingly
  // TODO: Use mxUtils.getStringSize for label bounds
  if (this.useBoundingBox) {
    var state = this.graph.getView().getState(cell);

    if (state != null && state.text != null && state.text.boundingBox != null) {
      var scale = this.graph.getView().scale;
      var tmp = state.text.boundingBox;

      var dx0 = Math.max(state.x - tmp.x, 0) / scale;
      var dy0 = Math.max(state.y - tmp.y, 0) / scale;
      var dx1 = Math.max(tmp.x + tmp.width - (state.x + state.width), 0) / scale;
      var dy1 = Math.max(tmp.y + tmp.height - (state.y + state.height), 0) / scale;

      geo = new mxRectangle(geo.x - dx0, geo.y - dy0, geo.width + dx0 + dx1, geo.height + dy0 + dy1);
    }
  }

  if (this.parent != null) {
    var parent = this.graph.getModel().getParent(cell);
    geo = geo.clone();

    if (parent != null && parent != this.parent) {
      var parentOffset = this.getParentOffset(parent);
      geo.x = geo.x + parentOffset.x;
      geo.y = geo.y + parentOffset.y;
    }
  }

  return new mxRectangle(geo.x, geo.y, geo.width, geo.height);
};

/**
 * Function: arrangeGroups
 *
 * Shortcut to <mxGraph.updateGroupBounds> with moveGroup set to true.
 */
mxGraphLayout.prototype.arrangeGroups = function (cells, border, topBorder, rightBorder, bottomBorder, leftBorder) {
  return this.graph.updateGroupBounds(cells, border, true, topBorder, rightBorder, bottomBorder, leftBorder);
};

/**
 * Class: WeightedCellSorter
 *
 * A utility class used to track cells whilst sorting occurs on the weighted
 * sum of their connected edges. Does not violate (x.compareTo(y)==0) ==
 * (x.equals(y))
 *
 * Constructor: WeightedCellSorter
 *
 * Constructs a new weighted cell sorted for the given cell and weight.
 */
function WeightedCellSorter(cell, weightedValue) {
  this.cell = cell;
  this.weightedValue = weightedValue;
}

/**
 * Variable: weightedValue
 *
 * The weighted value of the cell stored.
 */
WeightedCellSorter.prototype.weightedValue = 0;

/**
 * Variable: nudge
 *
 * Whether or not to flip equal weight values.
 */
WeightedCellSorter.prototype.nudge = false;

/**
 * Variable: visited
 *
 * Whether or not this cell has been visited in the current assignment.
 */
WeightedCellSorter.prototype.visited = false;

/**
 * Variable: rankIndex
 *
 * The index this cell is in the model rank.
 */
WeightedCellSorter.prototype.rankIndex = null;

/**
 * Variable: cell
 *
 * The cell whose median value is being calculated.
 */
WeightedCellSorter.prototype.cell = null;

/**
 * Function: compare
 *
 * Compares two WeightedCellSorters.
 */
WeightedCellSorter.prototype.compare = function (a, b) {
  if (a != null && b != null) {
    if (b.weightedValue > a.weightedValue) {
      return -1;
    } else if (b.weightedValue < a.weightedValue) {
      return 1;
    } else {
      if (b.nudge) {
        return -1;
      } else {
        return 1;
      }
    }
  } else {
    return 0;
  }
};

/**
 * layout/mxStackLayout.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxStackLayout
 *
 * Extends <mxGraphLayout> to create a horizontal or vertical stack of the
 * child vertices. The children do not need to be connected for this layout
 * to work.
 *
 * Example:
 *
 * (code)
 * var layout = new mxStackLayout(graph, true);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 * Constructor: mxStackLayout
 *
 * Constructs a new stack layout layout for the specified graph,
 * spacing, orientation and offset.
 */
function mxStackLayout(graph, horizontal, spacing, x0, y0, border) {
  mxGraphLayout.call(this, graph);
  this.horizontal = horizontal != null ? horizontal : true;
  this.spacing = spacing != null ? spacing : 0;
  this.x0 = x0 != null ? x0 : 0;
  this.y0 = y0 != null ? y0 : 0;
  this.border = border != null ? border : 0;
}

/**
 * Extends mxGraphLayout.
 */
mxStackLayout.prototype = new mxGraphLayout();
mxStackLayout.prototype.constructor = mxStackLayout;

/**
 * Variable: horizontal
 *
 * Specifies the orientation of the layout. Default is true.
 */
mxStackLayout.prototype.horizontal = null;

/**
 * Variable: spacing
 *
 * Specifies the spacing between the cells. Default is 0.
 */
mxStackLayout.prototype.spacing = null;

/**
 * Variable: x0
 *
 * Specifies the horizontal origin of the layout. Default is 0.
 */
mxStackLayout.prototype.x0 = null;

/**
 * Variable: y0
 *
 * Specifies the vertical origin of the layout. Default is 0.
 */
mxStackLayout.prototype.y0 = null;

/**
 * Variable: border
 *
 * Border to be added if fill is true. Default is 0.
 */
mxStackLayout.prototype.border = 0;

/**
 * Variable: marginTop
 *
 * Top margin for the child area. Default is 0.
 */
mxStackLayout.prototype.marginTop = 0;

/**
 * Variable: marginLeft
 *
 * Top margin for the child area. Default is 0.
 */
mxStackLayout.prototype.marginLeft = 0;

/**
 * Variable: marginRight
 *
 * Top margin for the child area. Default is 0.
 */
mxStackLayout.prototype.marginRight = 0;

/**
 * Variable: marginBottom
 *
 * Top margin for the child area. Default is 0.
 */
mxStackLayout.prototype.marginBottom = 0;

/**
 * Variable: keepFirstLocation
 *
 * Boolean indicating if the location of the first cell should be
 * kept, that is, it will not be moved to x0 or y0. Default is false.
 */
mxStackLayout.prototype.keepFirstLocation = false;

/**
 * Variable: fill
 *
 * Boolean indicating if dimension should be changed to fill out the parent
 * cell. Default is false.
 */
mxStackLayout.prototype.fill = false;

/**
 * Variable: resizeParent
 *
 * If the parent should be resized to match the width/height of the
 * stack. Default is false.
 */
mxStackLayout.prototype.resizeParent = false;

/**
 * Variable: resizeParentMax
 *
 * Use maximum of existing value and new value for resize of parent.
 * Default is false.
 */
mxStackLayout.prototype.resizeParentMax = false;

/**
 * Variable: resizeLast
 *
 * If the last element should be resized to fill out the parent. Default is
 * false. If <resizeParent> is true then this is ignored.
 */
mxStackLayout.prototype.resizeLast = false;

/**
 * Variable: wrap
 *
 * Value at which a new column or row should be created. Default is null.
 */
mxStackLayout.prototype.wrap = null;

/**
 * Variable: borderCollapse
 *
 * If the strokeWidth should be ignored. Default is true.
 */
mxStackLayout.prototype.borderCollapse = true;

/**
 * Variable: allowGaps
 *
 * If gaps should be allowed in the stack. Default is false.
 */
mxStackLayout.prototype.allowGaps = false;

/**
 * Variable: gridSize
 *
 * Grid size for alignment of position and size. Default is 0.
 */
mxStackLayout.prototype.gridSize = 0;

/**
 * Function: isHorizontal
 *
 * Returns <horizontal>.
 */
mxStackLayout.prototype.isHorizontal = function () {
  return this.horizontal;
};

/**
 * Function: moveCell
 *
 * Implements <mxGraphLayout.moveCell>.
 */
mxStackLayout.prototype.moveCell = function (cell, x, y) {
  var model = this.graph.getModel();
  var parent = model.getParent(cell);
  var horizontal = this.isHorizontal();

  if (cell != null && parent != null) {
    var i = 0;
    var last = 0;
    var childCount = model.getChildCount(parent);
    var value = horizontal ? x : y;
    var pstate = this.graph.getView().getState(parent);

    if (pstate != null) {
      value -= horizontal ? pstate.x : pstate.y;
    }

    value /= this.graph.view.scale;

    for (i = 0; i < childCount; i++) {
      var child = model.getChildAt(parent, i);

      if (child != cell) {
        var bounds = model.getGeometry(child);

        if (bounds != null) {
          var tmp = horizontal ? bounds.x + bounds.width / 2 : bounds.y + bounds.height / 2;

          if (last <= value && tmp > value) {
            break;
          }

          last = tmp;
        }
      }
    }

    // Changes child order in parent
    var idx = parent.getIndex(cell);
    idx = Math.max(0, i - (i > idx ? 1 : 0));

    model.add(parent, cell, idx);
  }
};

/**
 * Function: getParentSize
 *
 * Returns the size for the parent container or the size of the graph
 * container if the parent is a layer or the root of the model.
 */
mxStackLayout.prototype.getParentSize = function (parent) {
  var model = this.graph.getModel();
  var pgeo = model.getGeometry(parent);

  // Handles special case where the parent is either a layer with no
  // geometry or the current root of the view in which case the size
  // of the graph's container will be used.
  if (
    this.graph.container != null &&
    ((pgeo == null && model.isLayer(parent)) || parent == this.graph.getView().currentRoot)
  ) {
    var width = this.graph.container.offsetWidth - 1;
    var height = this.graph.container.offsetHeight - 1;
    pgeo = new mxRectangle(0, 0, width, height);
  }

  return pgeo;
};

/**
 * Function: getLayoutCells
 *
 * Returns the cells to be layouted.
 */
mxStackLayout.prototype.getLayoutCells = function (parent) {
  var model = this.graph.getModel();
  var childCount = model.getChildCount(parent);
  var cells = [];

  for (var i = 0; i < childCount; i++) {
    var child = model.getChildAt(parent, i);

    if (!this.isVertexIgnored(child) && this.isVertexMovable(child)) {
      cells.push(child);
    }
  }

  if (this.allowGaps) {
    cells.sort(
      mxUtils.bind(this, function (c1, c2) {
        var geo1 = this.graph.getCellGeometry(c1);
        var geo2 = this.graph.getCellGeometry(c2);

        return this.horizontal
          ? geo1.x == geo2.x
            ? 0
            : geo1.x > geo2.x > 0
            ? 1
            : -1
          : geo1.y == geo2.y
          ? 0
          : geo1.y > geo2.y > 0
          ? 1
          : -1;
      })
    );
  }

  return cells;
};

/**
 * Function: snap
 *
 * Snaps the given value to the grid size.
 */
mxStackLayout.prototype.snap = function (value) {
  if (this.gridSize != null && this.gridSize > 0) {
    value = Math.max(value, this.gridSize);

    if (value / this.gridSize > 1) {
      var mod = value % this.gridSize;
      value += mod > this.gridSize / 2 ? this.gridSize - mod : -mod;
    }
  }

  return value;
};

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>.
 *
 * Only children where <isVertexIgnored> returns false are taken into
 * account.
 */
mxStackLayout.prototype.execute = function (parent) {
  if (parent != null) {
    var pgeo = this.getParentSize(parent);
    var horizontal = this.isHorizontal();
    var model = this.graph.getModel();
    var fillValue = null;

    if (pgeo != null) {
      fillValue = horizontal
        ? pgeo.height - this.marginTop - this.marginBottom
        : pgeo.width - this.marginLeft - this.marginRight;
    }

    fillValue -= 2 * this.border;
    var x0 = this.x0 + this.border + this.marginLeft;
    var y0 = this.y0 + this.border + this.marginTop;

    // Handles swimlane start size
    if (this.graph.isSwimlane(parent)) {
      // Uses computed style to get latest
      var style = this.graph.getCellStyle(parent);
      var start = mxUtils.getNumber(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE);
      var horz = mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true) == 1;

      if (pgeo != null) {
        if (horz) {
          start = Math.min(start, pgeo.height);
        } else {
          start = Math.min(start, pgeo.width);
        }
      }

      if (horizontal == horz) {
        fillValue -= start;
      }

      if (horz) {
        y0 += start;
      } else {
        x0 += start;
      }
    }

    model.beginUpdate();
    try {
      var tmp = 0;
      var last = null;
      var lastValue = 0;
      var lastChild = null;
      var cells = this.getLayoutCells(parent);

      for (var i = 0; i < cells.length; i++) {
        var child = cells[i];
        var geo = model.getGeometry(child);

        if (geo != null) {
          geo = geo.clone();

          if (this.wrap != null && last != null) {
            if (
              (horizontal && last.x + last.width + geo.width + 2 * this.spacing > this.wrap) ||
              (!horizontal && last.y + last.height + geo.height + 2 * this.spacing > this.wrap)
            ) {
              last = null;

              if (horizontal) {
                y0 += tmp + this.spacing;
              } else {
                x0 += tmp + this.spacing;
              }

              tmp = 0;
            }
          }

          tmp = Math.max(tmp, horizontal ? geo.height : geo.width);
          var sw = 0;

          if (!this.borderCollapse) {
            var childStyle = this.graph.getCellStyle(child);
            sw = mxUtils.getNumber(childStyle, mxConstants.STYLE_STROKEWIDTH, 1);
          }

          if (last != null) {
            var temp = lastValue + this.spacing + Math.floor(sw / 2);

            if (horizontal) {
              geo.x = this.snap((this.allowGaps ? Math.max(temp, geo.x) : temp) - this.marginLeft) + this.marginLeft;
            } else {
              geo.y = this.snap((this.allowGaps ? Math.max(temp, geo.y) : temp) - this.marginTop) + this.marginTop;
            }
          } else if (!this.keepFirstLocation) {
            if (horizontal) {
              geo.x =
                this.allowGaps && geo.x > x0 ? Math.max(this.snap(geo.x - this.marginLeft) + this.marginLeft, x0) : x0;
            } else {
              geo.y =
                this.allowGaps && geo.y > y0 ? Math.max(this.snap(geo.y - this.marginTop) + this.marginTop, y0) : y0;
            }
          }

          if (horizontal) {
            geo.y = y0;
          } else {
            geo.x = x0;
          }

          if (this.fill && fillValue != null) {
            if (horizontal) {
              geo.height = fillValue;
            } else {
              geo.width = fillValue;
            }
          }

          if (horizontal) {
            geo.width = this.snap(geo.width);
          } else {
            geo.height = this.snap(geo.height);
          }

          this.setChildGeometry(child, geo);
          lastChild = child;
          last = geo;

          if (horizontal) {
            lastValue = last.x + last.width + Math.floor(sw / 2);
          } else {
            lastValue = last.y + last.height + Math.floor(sw / 2);
          }
        }
      }

      if (this.resizeParent && pgeo != null && last != null && !this.graph.isCellCollapsed(parent)) {
        this.updateParentGeometry(parent, pgeo, last);
      } else if (this.resizeLast && pgeo != null && last != null && lastChild != null) {
        if (horizontal) {
          last.width = pgeo.width - last.x - this.spacing - this.marginRight - this.marginLeft;
        } else {
          last.height = pgeo.height - last.y - this.spacing - this.marginBottom;
        }

        this.setChildGeometry(lastChild, last);
      }
    } finally {
      model.endUpdate();
    }
  }
};

/**
 * Function: setChildGeometry
 *
 * Sets the specific geometry to the given child cell.
 *
 * Parameters:
 *
 * child - The given child of <mxCell>.
 * geo - The specific geometry of <mxGeometry>.
 */
mxStackLayout.prototype.setChildGeometry = function (child, geo) {
  var geo2 = this.graph.getCellGeometry(child);

  if (geo2 == null || geo.x != geo2.x || geo.y != geo2.y || geo.width != geo2.width || geo.height != geo2.height) {
    this.graph.getModel().setGeometry(child, geo);
  }
};

/**
 * Function: updateParentGeometry
 *
 * Updates the geometry of the given parent cell.
 *
 * Parameters:
 *
 * parent - The given parent of <mxCell>.
 * pgeo - The new <mxGeometry> for parent.
 * last - The last <mxGeometry>.
 */
mxStackLayout.prototype.updateParentGeometry = function (parent, pgeo, last) {
  var horizontal = this.isHorizontal();
  var model = this.graph.getModel();

  var pgeo2 = pgeo.clone();

  if (horizontal) {
    var tmp = last.x + last.width + this.marginRight + this.border;

    if (this.resizeParentMax) {
      pgeo2.width = Math.max(pgeo2.width, tmp);
    } else {
      pgeo2.width = tmp;
    }
  } else {
    var tmp = last.y + last.height + this.marginBottom + this.border;

    if (this.resizeParentMax) {
      pgeo2.height = Math.max(pgeo2.height, tmp);
    } else {
      pgeo2.height = tmp;
    }
  }

  if (pgeo.x != pgeo2.x || pgeo.y != pgeo2.y || pgeo.width != pgeo2.width || pgeo.height != pgeo2.height) {
    model.setGeometry(parent, pgeo2);
  }
};

/**
 * layout/mxPartitionLayout.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPartitionLayout
 *
 * Extends <mxGraphLayout> for partitioning the parent cell vertically or
 * horizontally by filling the complete area with the child cells. A horizontal
 * layout partitions the height of the given parent whereas a a non-horizontal
 * layout partitions the width. If the parent is a layer (that is, a child of
 * the root node), then the current graph size is partitioned. The children do
 * not need to be connected for this layout to work.
 *
 * Example:
 *
 * (code)
 * var layout = new mxPartitionLayout(graph, true, 10, 20);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 * Constructor: mxPartitionLayout
 *
 * Constructs a new stack layout layout for the specified graph,
 * spacing, orientation and offset.
 */
function mxPartitionLayout(graph, horizontal, spacing, border) {
  mxGraphLayout.call(this, graph);
  this.horizontal = horizontal != null ? horizontal : true;
  this.spacing = spacing || 0;
  this.border = border || 0;
}

/**
 * Extends mxGraphLayout.
 */
mxPartitionLayout.prototype = new mxGraphLayout();
mxPartitionLayout.prototype.constructor = mxPartitionLayout;

/**
 * Variable: horizontal
 *
 * Boolean indicating the direction in which the space is partitioned.
 * Default is true.
 */
mxPartitionLayout.prototype.horizontal = null;

/**
 * Variable: spacing
 *
 * Integer that specifies the absolute spacing in pixels between the
 * children. Default is 0.
 */
mxPartitionLayout.prototype.spacing = null;

/**
 * Variable: border
 *
 * Integer that specifies the absolute inset in pixels for the parent that
 * contains the children. Default is 0.
 */
mxPartitionLayout.prototype.border = null;

/**
 * Variable: resizeVertices
 *
 * Boolean that specifies if vertices should be resized. Default is true.
 */
mxPartitionLayout.prototype.resizeVertices = true;

/**
 * Function: isHorizontal
 *
 * Returns <horizontal>.
 */
mxPartitionLayout.prototype.isHorizontal = function () {
  return this.horizontal;
};

/**
 * Function: moveCell
 *
 * Implements <mxGraphLayout.moveCell>.
 */
mxPartitionLayout.prototype.moveCell = function (cell, x, y) {
  var model = this.graph.getModel();
  var parent = model.getParent(cell);

  if (cell != null && parent != null) {
    var i = 0;
    var last = 0;
    var childCount = model.getChildCount(parent);

    // Finds index of the closest swimlane
    // TODO: Take into account the orientation
    for (i = 0; i < childCount; i++) {
      var child = model.getChildAt(parent, i);
      var bounds = this.getVertexBounds(child);

      if (bounds != null) {
        var tmp = bounds.x + bounds.width / 2;

        if (last < x && tmp > x) {
          break;
        }

        last = tmp;
      }
    }

    // Changes child order in parent
    var idx = parent.getIndex(cell);
    idx = Math.max(0, i - (i > idx ? 1 : 0));

    model.add(parent, cell, idx);
  }
};

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>. All children where <isVertexIgnored>
 * returns false and <isVertexMovable> returns true are modified.
 */
mxPartitionLayout.prototype.execute = function (parent) {
  var horizontal = this.isHorizontal();
  var model = this.graph.getModel();
  var pgeo = model.getGeometry(parent);

  // Handles special case where the parent is either a layer with no
  // geometry or the current root of the view in which case the size
  // of the graph's container will be used.
  if (
    this.graph.container != null &&
    ((pgeo == null && model.isLayer(parent)) || parent == this.graph.getView().currentRoot)
  ) {
    var width = this.graph.container.offsetWidth - 1;
    var height = this.graph.container.offsetHeight - 1;
    pgeo = new mxRectangle(0, 0, width, height);
  }

  if (pgeo != null) {
    var children = [];
    var childCount = model.getChildCount(parent);

    for (var i = 0; i < childCount; i++) {
      var child = model.getChildAt(parent, i);

      if (!this.isVertexIgnored(child) && this.isVertexMovable(child)) {
        children.push(child);
      }
    }

    var n = children.length;

    if (n > 0) {
      var x0 = this.border;
      var y0 = this.border;
      var other = horizontal ? pgeo.height : pgeo.width;
      other -= 2 * this.border;

      var size = this.graph.isSwimlane(parent) ? this.graph.getStartSize(parent) : new mxRectangle();

      other -= horizontal ? size.height : size.width;
      x0 = x0 + size.width;
      y0 = y0 + size.height;

      var tmp = this.border + (n - 1) * this.spacing;
      var value = horizontal ? (pgeo.width - x0 - tmp) / n : (pgeo.height - y0 - tmp) / n;

      // Avoids negative values, that is values where the sum of the
      // spacing plus the border is larger then the available space
      if (value > 0) {
        model.beginUpdate();
        try {
          for (var i = 0; i < n; i++) {
            var child = children[i];
            var geo = model.getGeometry(child);

            if (geo != null) {
              geo = geo.clone();
              geo.x = x0;
              geo.y = y0;

              if (horizontal) {
                if (this.resizeVertices) {
                  geo.width = value;
                  geo.height = other;
                }

                x0 += value + this.spacing;
              } else {
                if (this.resizeVertices) {
                  geo.height = value;
                  geo.width = other;
                }

                y0 += value + this.spacing;
              }

              model.setGeometry(child, geo);
            }
          }
        } finally {
          model.endUpdate();
        }
      }
    }
  }
};

/**
 * layout/mxCompactTreeLayout.js
 */
/**
 * Copyright (c) 2006-2018, JGraph Ltd
 * Copyright (c) 2006-2018, Gaudenz Alder
 */
/**
 * Class: mxCompactTreeLayout
 *
 * Extends <mxGraphLayout> to implement a compact tree (Moen) algorithm. This
 * layout is suitable for graphs that have no cycles (trees). Vertices that are
 * not connected to the tree will be ignored by this layout.
 *
 * Example:
 *
 * (code)
 * var layout = new mxCompactTreeLayout(graph);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 * Constructor: mxCompactTreeLayout
 *
 * Constructs a new compact tree layout for the specified graph
 * and orientation.
 */
function mxCompactTreeLayout(graph, horizontal, invert) {
  mxGraphLayout.call(this, graph);
  this.horizontal = horizontal != null ? horizontal : true;
  this.invert = invert != null ? invert : false;
}

/**
 * Extends mxGraphLayout.
 */
mxCompactTreeLayout.prototype = new mxGraphLayout();
mxCompactTreeLayout.prototype.constructor = mxCompactTreeLayout;

/**
 * Variable: horizontal
 *
 * Specifies the orientation of the layout. Default is true.
 */
mxCompactTreeLayout.prototype.horizontal = null;

/**
 * Variable: invert
 *
 * Specifies if edge directions should be inverted. Default is false.
 */
mxCompactTreeLayout.prototype.invert = null;

/**
 * Variable: resizeParent
 *
 * If the parents should be resized to match the width/height of the
 * children. Default is true.
 */
mxCompactTreeLayout.prototype.resizeParent = true;

/**
 * Variable: maintainParentLocation
 *
 * Specifies if the parent location should be maintained, so that the
 * top, left corner stays the same before and after execution of
 * the layout. Default is false for backwards compatibility.
 */
mxCompactTreeLayout.prototype.maintainParentLocation = false;

/**
 * Variable: groupPadding
 *
 * Padding added to resized parents. Default is 10.
 */
mxCompactTreeLayout.prototype.groupPadding = 10;

/**
 * Variable: groupPaddingTop
 *
 * Top padding added to resized parents. Default is 0.
 */
mxCompactTreeLayout.prototype.groupPaddingTop = 0;

/**
 * Variable: groupPaddingRight
 *
 * Right padding added to resized parents. Default is 0.
 */
mxCompactTreeLayout.prototype.groupPaddingRight = 0;

/**
 * Variable: groupPaddingBottom
 *
 * Bottom padding added to resized parents. Default is 0.
 */
mxCompactTreeLayout.prototype.groupPaddingBottom = 0;

/**
 * Variable: groupPaddingLeft
 *
 * Left padding added to resized parents. Default is 0.
 */
mxCompactTreeLayout.prototype.groupPaddingLeft = 0;

/**
 * Variable: parentsChanged
 *
 * A set of the parents that need updating based on children
 * process as part of the layout.
 */
mxCompactTreeLayout.prototype.parentsChanged = null;

/**
 * Variable: moveTree
 *
 * Specifies if the tree should be moved to the top, left corner
 * if it is inside a top-level layer. Default is false.
 */
mxCompactTreeLayout.prototype.moveTree = false;

/**
 * Variable: visited
 *
 * Specifies if the tree should be moved to the top, left corner
 * if it is inside a top-level layer. Default is false.
 */
mxCompactTreeLayout.prototype.visited = null;

/**
 * Variable: levelDistance
 *
 * Holds the levelDistance. Default is 10.
 */
mxCompactTreeLayout.prototype.levelDistance = 10;

/**
 * Variable: nodeDistance
 *
 * Holds the nodeDistance. Default is 20.
 */
mxCompactTreeLayout.prototype.nodeDistance = 20;

/**
 * Variable: resetEdges
 *
 * Specifies if all edge points of traversed edges should be removed.
 * Default is true.
 */
mxCompactTreeLayout.prototype.resetEdges = true;

/**
 * Variable: prefHozEdgeSep
 *
 * The preferred horizontal distance between edges exiting a vertex.
 */
mxCompactTreeLayout.prototype.prefHozEdgeSep = 5;

/**
 * Variable: prefVertEdgeOff
 *
 * The preferred vertical offset between edges exiting a vertex.
 */
mxCompactTreeLayout.prototype.prefVertEdgeOff = 4;

/**
 * Variable: minEdgeJetty
 *
 * The minimum distance for an edge jetty from a vertex.
 */
mxCompactTreeLayout.prototype.minEdgeJetty = 8;

/**
 * Variable: channelBuffer
 *
 * The size of the vertical buffer in the center of inter-rank channels
 * where edge control points should not be placed.
 */
mxCompactTreeLayout.prototype.channelBuffer = 4;

/**
 * Variable: edgeRouting
 *
 * Whether or not to apply the internal tree edge routing.
 */
mxCompactTreeLayout.prototype.edgeRouting = true;

/**
 * Variable: sortEdges
 *
 * Specifies if edges should be sorted according to the order of their
 * opposite terminal cell in the model.
 */
mxCompactTreeLayout.prototype.sortEdges = false;

/**
 * Variable: alignRanks
 *
 * Whether or not the tops of cells in each rank should be aligned
 * across the rank
 */
mxCompactTreeLayout.prototype.alignRanks = false;

/**
 * Variable: maxRankHeight
 *
 * An array of the maximum height of cells (relative to the layout direction)
 * per rank
 */
mxCompactTreeLayout.prototype.maxRankHeight = null;

/**
 * Variable: root
 *
 * The cell to use as the root of the tree
 */
mxCompactTreeLayout.prototype.root = null;

/**
 * Variable: node
 *
 * The internal node representation of the root cell. Do not set directly
 * , this value is only exposed to assist with post-processing functionality
 */
mxCompactTreeLayout.prototype.node = null;

/**
 * Function: isVertexIgnored
 *
 * Returns a boolean indicating if the given <mxCell> should be ignored as a
 * vertex. This returns true if the cell has no connections.
 *
 * Parameters:
 *
 * vertex - <mxCell> whose ignored state should be returned.
 */
mxCompactTreeLayout.prototype.isVertexIgnored = function (vertex) {
  return (
    mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) || this.graph.getConnections(vertex).length == 0
  );
};

/**
 * Function: isHorizontal
 *
 * Returns <horizontal>.
 */
mxCompactTreeLayout.prototype.isHorizontal = function () {
  return this.horizontal;
};

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>.
 *
 * If the parent has any connected edges, then it is used as the root of
 * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
 * root node within the set of children of the given parent.
 *
 * Parameters:
 *
 * parent - <mxCell> whose children should be laid out.
 * root - Optional <mxCell> that will be used as the root of the tree.
 * Overrides <root> if specified.
 */
mxCompactTreeLayout.prototype.execute = function (parent, root) {
  this.parent = parent;
  var model = this.graph.getModel();

  if (root == null) {
    // Takes the parent as the root if it has outgoing edges
    if (this.graph.getEdges(parent, model.getParent(parent), this.invert, !this.invert, false).length > 0) {
      this.root = parent;
    }

    // Tries to find a suitable root in the parent's
    // children
    else {
      var roots = this.graph.findTreeRoots(parent, true, this.invert);

      if (roots.length > 0) {
        for (var i = 0; i < roots.length; i++) {
          if (
            !this.isVertexIgnored(roots[i]) &&
            this.graph.getEdges(roots[i], null, this.invert, !this.invert, false).length > 0
          ) {
            this.root = roots[i];
            break;
          }
        }
      }
    }
  } else {
    this.root = root;
  }

  if (this.root != null) {
    if (this.resizeParent) {
      this.parentsChanged = new Object();
    } else {
      this.parentsChanged = null;
    }

    //  Maintaining parent location
    this.parentX = null;
    this.parentY = null;

    if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation) {
      var geo = this.graph.getCellGeometry(parent);

      if (geo != null) {
        this.parentX = geo.x;
        this.parentY = geo.y;
      }
    }

    model.beginUpdate();

    try {
      this.visited = new Object();
      this.node = this.dfs(this.root, parent);

      if (this.alignRanks) {
        this.maxRankHeight = [];
        this.findRankHeights(this.node, 0);
        this.setCellHeights(this.node, 0);
      }

      if (this.node != null) {
        this.layout(this.node);
        var x0 = this.graph.gridSize;
        var y0 = x0;

        if (!this.moveTree) {
          var g = this.getVertexBounds(this.root);

          if (g != null) {
            x0 = g.x;
            y0 = g.y;
          }
        }

        var bounds = null;

        if (this.isHorizontal()) {
          bounds = this.horizontalLayout(this.node, x0, y0);
        } else {
          bounds = this.verticalLayout(this.node, null, x0, y0);
        }

        if (bounds != null) {
          var dx = 0;
          var dy = 0;

          if (bounds.x < 0) {
            dx = Math.abs(x0 - bounds.x);
          }

          if (bounds.y < 0) {
            dy = Math.abs(y0 - bounds.y);
          }

          if (dx != 0 || dy != 0) {
            this.moveNode(this.node, dx, dy);
          }

          if (this.resizeParent) {
            this.adjustParents();
          }

          if (this.edgeRouting) {
            // Iterate through all edges setting their positions
            this.localEdgeProcessing(this.node);
          }
        }

        // Maintaining parent location
        if (this.parentX != null && this.parentY != null) {
          var geo = this.graph.getCellGeometry(parent);

          if (geo != null) {
            geo = geo.clone();
            geo.x = this.parentX;
            geo.y = this.parentY;
            model.setGeometry(parent, geo);
          }
        }
      }
    } finally {
      model.endUpdate();
    }
  }
};

/**
 * Function: moveNode
 *
 * Moves the specified node and all of its children by the given amount.
 */
mxCompactTreeLayout.prototype.moveNode = function (node, dx, dy) {
  node.x += dx;
  node.y += dy;
  this.apply(node);

  var child = node.child;

  while (child != null) {
    this.moveNode(child, dx, dy);
    child = child.next;
  }
};

/**
 * Function: sortOutgoingEdges
 *
 * Called if <sortEdges> is true to sort the array of outgoing edges in place.
 */
mxCompactTreeLayout.prototype.sortOutgoingEdges = function (source, edges) {
  var lookup = new mxDictionary();

  edges.sort(function (e1, e2) {
    var end1 = e1.getTerminal(e1.getTerminal(false) == source);
    var p1 = lookup.get(end1);

    if (p1 == null) {
      p1 = mxCellPath.create(end1).split(mxCellPath.PATH_SEPARATOR);
      lookup.put(end1, p1);
    }

    var end2 = e2.getTerminal(e2.getTerminal(false) == source);
    var p2 = lookup.get(end2);

    if (p2 == null) {
      p2 = mxCellPath.create(end2).split(mxCellPath.PATH_SEPARATOR);
      lookup.put(end2, p2);
    }

    return mxCellPath.compare(p1, p2);
  });
};

/**
 * Function: findRankHeights
 *
 * Stores the maximum height (relative to the layout
 * direction) of cells in each rank
 */
mxCompactTreeLayout.prototype.findRankHeights = function (node, rank) {
  if (this.maxRankHeight[rank] == null || this.maxRankHeight[rank] < node.height) {
    this.maxRankHeight[rank] = node.height;
  }

  var child = node.child;

  while (child != null) {
    this.findRankHeights(child, rank + 1);
    child = child.next;
  }
};

/**
 * Function: setCellHeights
 *
 * Set the cells heights (relative to the layout
 * direction) when the tops of each rank are to be aligned
 */
mxCompactTreeLayout.prototype.setCellHeights = function (node, rank) {
  if (this.maxRankHeight[rank] != null && this.maxRankHeight[rank] > node.height) {
    node.height = this.maxRankHeight[rank];
  }

  var child = node.child;

  while (child != null) {
    this.setCellHeights(child, rank + 1);
    child = child.next;
  }
};

/**
 * Function: dfs
 *
 * Does a depth first search starting at the specified cell.
 * Makes sure the specified parent is never left by the
 * algorithm.
 */
mxCompactTreeLayout.prototype.dfs = function (cell, parent) {
  var id = mxCellPath.create(cell);
  var node = null;

  if (cell != null && this.visited[id] == null && !this.isVertexIgnored(cell)) {
    this.visited[id] = cell;
    node = this.createNode(cell);

    var model = this.graph.getModel();
    var prev = null;
    var out = this.graph.getEdges(cell, parent, this.invert, !this.invert, false, true);
    var view = this.graph.getView();

    if (this.sortEdges) {
      this.sortOutgoingEdges(cell, out);
    }

    for (var i = 0; i < out.length; i++) {
      var edge = out[i];

      if (!this.isEdgeIgnored(edge)) {
        // Resets the points on the traversed edge
        if (this.resetEdges) {
          this.setEdgePoints(edge, null);
        }

        if (this.edgeRouting) {
          this.setEdgeStyleEnabled(edge, false);
          this.setEdgePoints(edge, null);
        }

        // Checks if terminal in same swimlane
        var state = view.getState(edge);
        var target = state != null ? state.getVisibleTerminal(this.invert) : view.getVisibleTerminal(edge, this.invert);
        var tmp = this.dfs(target, parent);

        if (tmp != null && model.getGeometry(target) != null) {
          if (prev == null) {
            node.child = tmp;
          } else {
            prev.next = tmp;
          }

          prev = tmp;
        }
      }
    }
  }

  return node;
};

/**
 * Function: layout
 *
 * Starts the actual compact tree layout algorithm
 * at the given node.
 */
mxCompactTreeLayout.prototype.layout = function (node) {
  if (node != null) {
    var child = node.child;

    while (child != null) {
      this.layout(child);
      child = child.next;
    }

    if (node.child != null) {
      this.attachParent(node, this.join(node));
    } else {
      this.layoutLeaf(node);
    }
  }
};

/**
 * Function: horizontalLayout
 */
mxCompactTreeLayout.prototype.horizontalLayout = function (node, x0, y0, bounds) {
  node.x += x0 + node.offsetX;
  node.y += y0 + node.offsetY;
  bounds = this.apply(node, bounds);
  var child = node.child;

  if (child != null) {
    bounds = this.horizontalLayout(child, node.x, node.y, bounds);
    var siblingOffset = node.y + child.offsetY;
    var s = child.next;

    while (s != null) {
      bounds = this.horizontalLayout(s, node.x + child.offsetX, siblingOffset, bounds);
      siblingOffset += s.offsetY;
      s = s.next;
    }
  }

  return bounds;
};

/**
 * Function: verticalLayout
 */
mxCompactTreeLayout.prototype.verticalLayout = function (node, parent, x0, y0, bounds) {
  node.x += x0 + node.offsetY;
  node.y += y0 + node.offsetX;
  bounds = this.apply(node, bounds);
  var child = node.child;

  if (child != null) {
    bounds = this.verticalLayout(child, node, node.x, node.y, bounds);
    var siblingOffset = node.x + child.offsetY;
    var s = child.next;

    while (s != null) {
      bounds = this.verticalLayout(s, node, siblingOffset, node.y + child.offsetX, bounds);
      siblingOffset += s.offsetY;
      s = s.next;
    }
  }

  return bounds;
};

/**
 * Function: attachParent
 */
mxCompactTreeLayout.prototype.attachParent = function (node, height) {
  var x = this.nodeDistance + this.levelDistance;
  var y2 = (height - node.width) / 2 - this.nodeDistance;
  var y1 = y2 + node.width + 2 * this.nodeDistance - height;

  node.child.offsetX = x + node.height;
  node.child.offsetY = y1;

  node.contour.upperHead = this.createLine(node.height, 0, this.createLine(x, y1, node.contour.upperHead));
  node.contour.lowerHead = this.createLine(node.height, 0, this.createLine(x, y2, node.contour.lowerHead));
};

/**
 * Function: layoutLeaf
 */
mxCompactTreeLayout.prototype.layoutLeaf = function (node) {
  var dist = 2 * this.nodeDistance;

  node.contour.upperTail = this.createLine(node.height + dist, 0);
  node.contour.upperHead = node.contour.upperTail;
  node.contour.lowerTail = this.createLine(0, -node.width - dist);
  node.contour.lowerHead = this.createLine(node.height + dist, 0, node.contour.lowerTail);
};

/**
 * Function: join
 */
mxCompactTreeLayout.prototype.join = function (node) {
  var dist = 2 * this.nodeDistance;

  var child = node.child;
  node.contour = child.contour;
  var h = child.width + dist;
  var sum = h;
  child = child.next;

  while (child != null) {
    var d = this.merge(node.contour, child.contour);
    child.offsetY = d + h;
    child.offsetX = 0;
    h = child.width + dist;
    sum += d + h;
    child = child.next;
  }

  return sum;
};

/**
 * Function: merge
 */
mxCompactTreeLayout.prototype.merge = function (p1, p2) {
  var x = 0;
  var y = 0;
  var total = 0;

  var upper = p1.lowerHead;
  var lower = p2.upperHead;

  while (lower != null && upper != null) {
    var d = this.offset(x, y, lower.dx, lower.dy, upper.dx, upper.dy);
    y += d;
    total += d;

    if (x + lower.dx <= upper.dx) {
      x += lower.dx;
      y += lower.dy;
      lower = lower.next;
    } else {
      x -= upper.dx;
      y -= upper.dy;
      upper = upper.next;
    }
  }

  if (lower != null) {
    var b = this.bridge(p1.upperTail, 0, 0, lower, x, y);
    p1.upperTail = b.next != null ? p2.upperTail : b;
    p1.lowerTail = p2.lowerTail;
  } else {
    var b = this.bridge(p2.lowerTail, x, y, upper, 0, 0);

    if (b.next == null) {
      p1.lowerTail = b;
    }
  }

  p1.lowerHead = p2.lowerHead;

  return total;
};

/**
 * Function: offset
 */
mxCompactTreeLayout.prototype.offset = function (p1, p2, a1, a2, b1, b2) {
  var d = 0;

  if (b1 <= p1 || p1 + a1 <= 0) {
    return 0;
  }

  var t = b1 * a2 - a1 * b2;

  if (t > 0) {
    if (p1 < 0) {
      var s = p1 * a2;
      d = s / a1 - p2;
    } else if (p1 > 0) {
      var s = p1 * b2;
      d = s / b1 - p2;
    } else {
      d = -p2;
    }
  } else if (b1 < p1 + a1) {
    var s = (b1 - p1) * a2;
    d = b2 - (p2 + s / a1);
  } else if (b1 > p1 + a1) {
    var s = (a1 + p1) * b2;
    d = s / b1 - (p2 + a2);
  } else {
    d = b2 - (p2 + a2);
  }

  if (d > 0) {
    return d;
  } else {
    return 0;
  }
};

/**
 * Function: bridge
 */
mxCompactTreeLayout.prototype.bridge = function (line1, x1, y1, line2, x2, y2) {
  var dx = x2 + line2.dx - x1;
  var dy = 0;
  var s = 0;

  if (line2.dx == 0) {
    dy = line2.dy;
  } else {
    s = dx * line2.dy;
    dy = s / line2.dx;
  }

  var r = this.createLine(dx, dy, line2.next);
  line1.next = this.createLine(0, y2 + line2.dy - dy - y1, r);

  return r;
};

/**
 * Function: createNode
 */
mxCompactTreeLayout.prototype.createNode = function (cell) {
  var node = new Object();
  node.cell = cell;
  node.x = 0;
  node.y = 0;
  node.width = 0;
  node.height = 0;

  var geo = this.getVertexBounds(cell);

  if (geo != null) {
    if (this.isHorizontal()) {
      node.width = geo.height;
      node.height = geo.width;
    } else {
      node.width = geo.width;
      node.height = geo.height;
    }
  }

  node.offsetX = 0;
  node.offsetY = 0;
  node.contour = new Object();

  return node;
};

/**
 * Function: apply
 */
mxCompactTreeLayout.prototype.apply = function (node, bounds) {
  var model = this.graph.getModel();
  var cell = node.cell;
  var g = model.getGeometry(cell);

  if (cell != null && g != null) {
    if (this.isVertexMovable(cell)) {
      g = this.setVertexLocation(cell, node.x, node.y);

      if (this.resizeParent) {
        var parent = model.getParent(cell);
        var id = mxCellPath.create(parent);

        // Implements set semantic
        if (this.parentsChanged[id] == null) {
          this.parentsChanged[id] = parent;
        }
      }
    }

    if (bounds == null) {
      bounds = new mxRectangle(g.x, g.y, g.width, g.height);
    } else {
      bounds = new mxRectangle(
        Math.min(bounds.x, g.x),
        Math.min(bounds.y, g.y),
        Math.max(bounds.x + bounds.width, g.x + g.width),
        Math.max(bounds.y + bounds.height, g.y + g.height)
      );
    }
  }

  return bounds;
};

/**
 * Function: createLine
 */
mxCompactTreeLayout.prototype.createLine = function (dx, dy, next) {
  var line = new Object();
  line.dx = dx;
  line.dy = dy;
  line.next = next;

  return line;
};

/**
 * Function: adjustParents
 *
 * Adjust parent cells whose child geometries have changed. The default
 * implementation adjusts the group to just fit around the children with
 * a padding.
 */
mxCompactTreeLayout.prototype.adjustParents = function () {
  var tmp = [];

  for (var id in this.parentsChanged) {
    tmp.push(this.parentsChanged[id]);
  }

  this.arrangeGroups(
    mxUtils.sortCells(tmp, true),
    this.groupPadding,
    this.groupPaddingTop,
    this.groupPaddingRight,
    this.groupPaddingBottom,
    this.groupPaddingLeft
  );
};

/**
 * Function: localEdgeProcessing
 *
 * Moves the specified node and all of its children by the given amount.
 */
mxCompactTreeLayout.prototype.localEdgeProcessing = function (node) {
  this.processNodeOutgoing(node);
  var child = node.child;

  while (child != null) {
    this.localEdgeProcessing(child);
    child = child.next;
  }
};

/**
 * Function: processNodeOutgoing
 *
 * Separates the x position of edges as they connect to vertices
 */
mxCompactTreeLayout.prototype.processNodeOutgoing = function (node) {
  var child = node.child;
  var parentCell = node.cell;

  var childCount = 0;
  var sortedCells = [];

  while (child != null) {
    childCount++;

    var sortingCriterion = child.x;

    if (this.horizontal) {
      sortingCriterion = child.y;
    }

    sortedCells.push(new WeightedCellSorter(child, sortingCriterion));
    child = child.next;
  }

  sortedCells.sort(WeightedCellSorter.prototype.compare);

  var availableWidth = node.width;

  var requiredWidth = (childCount + 1) * this.prefHozEdgeSep;

  // Add a buffer on the edges of the vertex if the edge count allows
  if (availableWidth > requiredWidth + 2 * this.prefHozEdgeSep) {
    availableWidth -= 2 * this.prefHozEdgeSep;
  }

  var edgeSpacing = availableWidth / childCount;

  var currentXOffset = edgeSpacing / 2.0;

  if (availableWidth > requiredWidth + 2 * this.prefHozEdgeSep) {
    currentXOffset += this.prefHozEdgeSep;
  }

  var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
  var maxYOffset = 0;

  var parentBounds = this.getVertexBounds(parentCell);
  child = node.child;

  for (var j = 0; j < sortedCells.length; j++) {
    var childCell = sortedCells[j].cell.cell;
    var childBounds = this.getVertexBounds(childCell);

    var edges = this.graph.getEdgesBetween(parentCell, childCell, false);

    var newPoints = [];
    var x = 0;
    var y = 0;

    for (var i = 0; i < edges.length; i++) {
      if (this.horizontal) {
        // Use opposite co-ords, calculation was done for
        //
        x = parentBounds.x + parentBounds.width;
        y = parentBounds.y + currentXOffset;
        newPoints.push(new mxPoint(x, y));
        x = parentBounds.x + parentBounds.width + currentYOffset;
        newPoints.push(new mxPoint(x, y));
        y = childBounds.y + childBounds.height / 2.0;
        newPoints.push(new mxPoint(x, y));
        this.setEdgePoints(edges[i], newPoints);
      } else {
        x = parentBounds.x + currentXOffset;
        y = parentBounds.y + parentBounds.height;
        newPoints.push(new mxPoint(x, y));
        y = parentBounds.y + parentBounds.height + currentYOffset;
        newPoints.push(new mxPoint(x, y));
        x = childBounds.x + childBounds.width / 2.0;
        newPoints.push(new mxPoint(x, y));
        this.setEdgePoints(edges[i], newPoints);
      }
    }

    if (j < childCount / 2) {
      currentYOffset += this.prefVertEdgeOff;
    } else if (j > childCount / 2) {
      currentYOffset -= this.prefVertEdgeOff;
    }
    // Ignore the case if equals, this means the second of 2
    // jettys with the same y (even number of edges)

    //								pos[k * 2] = currentX;
    currentXOffset += edgeSpacing;
    //								pos[k * 2 + 1] = currentYOffset;

    maxYOffset = Math.max(maxYOffset, currentYOffset);
  }
};

/**
 * layout/mxRadialTreeLayout.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxRadialTreeLayout
 *
 * Extends <mxGraphLayout> to implement a radial tree algorithm. This
 * layout is suitable for graphs that have no cycles (trees). Vertices that are
 * not connected to the tree will be ignored by this layout.
 *
 * Example:
 *
 * (code)
 * var layout = new mxRadialTreeLayout(graph);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 * Constructor: mxRadialTreeLayout
 *
 * Constructs a new radial tree layout for the specified graph
 */
function mxRadialTreeLayout(graph) {
  mxCompactTreeLayout.call(this, graph, false);
}

/**
 * Extends mxGraphLayout.
 */
mxUtils.extend(mxRadialTreeLayout, mxCompactTreeLayout);

/**
 * Variable: angleOffset
 *
 * The initial offset to compute the angle position.
 */
mxRadialTreeLayout.prototype.angleOffset = 0.5;

/**
 * Variable: rootx
 *
 * The X co-ordinate of the root cell
 */
mxRadialTreeLayout.prototype.rootx = 0;

/**
 * Variable: rooty
 *
 * The Y co-ordinate of the root cell
 */
mxRadialTreeLayout.prototype.rooty = 0;

/**
 * Variable: levelDistance
 *
 * Holds the levelDistance. Default is 120.
 */
mxRadialTreeLayout.prototype.levelDistance = 120;

/**
 * Variable: nodeDistance
 *
 * Holds the nodeDistance. Default is 10.
 */
mxRadialTreeLayout.prototype.nodeDistance = 10;

/**
 * Variable: autoRadius
 *
 * Specifies if the radios should be computed automatically
 */
mxRadialTreeLayout.prototype.autoRadius = false;

/**
 * Variable: sortEdges
 *
 * Specifies if edges should be sorted according to the order of their
 * opposite terminal cell in the model.
 */
mxRadialTreeLayout.prototype.sortEdges = false;

/**
 * Variable: rowMinX
 *
 * Array of leftmost x coordinate of each row
 */
mxRadialTreeLayout.prototype.rowMinX = [];

/**
 * Variable: rowMaxX
 *
 * Array of rightmost x coordinate of each row
 */
mxRadialTreeLayout.prototype.rowMaxX = [];

/**
 * Variable: rowMinCenX
 *
 * Array of x coordinate of leftmost vertex of each row
 */
mxRadialTreeLayout.prototype.rowMinCenX = [];

/**
 * Variable: rowMaxCenX
 *
 * Array of x coordinate of rightmost vertex of each row
 */
mxRadialTreeLayout.prototype.rowMaxCenX = [];

/**
 * Variable: rowRadi
 *
 * Array of y deltas of each row behind root vertex, also the radius in the tree
 */
mxRadialTreeLayout.prototype.rowRadi = [];

/**
 * Variable: row
 *
 * Array of vertices on each row
 */
mxRadialTreeLayout.prototype.row = [];

/**
 * Function: isVertexIgnored
 *
 * Returns a boolean indicating if the given <mxCell> should be ignored as a
 * vertex. This returns true if the cell has no connections.
 *
 * Parameters:
 *
 * vertex - <mxCell> whose ignored state should be returned.
 */
mxRadialTreeLayout.prototype.isVertexIgnored = function (vertex) {
  return (
    mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) || this.graph.getConnections(vertex).length == 0
  );
};

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>.
 *
 * If the parent has any connected edges, then it is used as the root of
 * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
 * root node within the set of children of the given parent.
 *
 * Parameters:
 *
 * parent - <mxCell> whose children should be laid out.
 * root - Optional <mxCell> that will be used as the root of the tree.
 */
mxRadialTreeLayout.prototype.execute = function (parent, root) {
  this.parent = parent;

  this.useBoundingBox = false;
  this.edgeRouting = false;
  //this.horizontal = false;

  mxCompactTreeLayout.prototype.execute.apply(this, arguments);

  var bounds = null;
  var rootBounds = this.getVertexBounds(this.root);
  this.centerX = rootBounds.x + rootBounds.width / 2;
  this.centerY = rootBounds.y + rootBounds.height / 2;

  // Calculate the bounds of the involved vertices directly from the values set in the compact tree
  for (var vertex in this.visited) {
    var vertexBounds = this.getVertexBounds(this.visited[vertex]);
    bounds = bounds != null ? bounds : vertexBounds.clone();
    bounds.add(vertexBounds);
  }

  this.calcRowDims([this.node], 0);

  var maxLeftGrad = 0;
  var maxRightGrad = 0;

  // Find the steepest left and right gradients
  for (var i = 0; i < this.row.length; i++) {
    var leftGrad = (this.centerX - this.rowMinX[i] - this.nodeDistance) / this.rowRadi[i];
    var rightGrad = (this.rowMaxX[i] - this.centerX - this.nodeDistance) / this.rowRadi[i];

    maxLeftGrad = Math.max(maxLeftGrad, leftGrad);
    maxRightGrad = Math.max(maxRightGrad, rightGrad);
  }

  // Extend out row so they meet the maximum gradient and convert to polar co-ords
  for (var i = 0; i < this.row.length; i++) {
    var xLeftLimit = this.centerX - this.nodeDistance - maxLeftGrad * this.rowRadi[i];
    var xRightLimit = this.centerX + this.nodeDistance + maxRightGrad * this.rowRadi[i];
    var fullWidth = xRightLimit - xLeftLimit;

    for (var j = 0; j < this.row[i].length; j++) {
      var row = this.row[i];
      var node = row[j];
      var vertexBounds = this.getVertexBounds(node.cell);
      var xProportion = (vertexBounds.x + vertexBounds.width / 2 - xLeftLimit) / fullWidth;
      var theta = 2 * Math.PI * xProportion;
      node.theta = theta;
    }
  }

  // Post-process from outside inwards to try to align parents with children
  for (var i = this.row.length - 2; i >= 0; i--) {
    var row = this.row[i];

    for (var j = 0; j < row.length; j++) {
      var node = row[j];
      var child = node.child;
      var counter = 0;
      var totalTheta = 0;

      while (child != null) {
        totalTheta += child.theta;
        counter++;
        child = child.next;
      }

      if (counter > 0) {
        var averTheta = totalTheta / counter;

        if (averTheta > node.theta && j < row.length - 1) {
          var nextTheta = row[j + 1].theta;
          node.theta = Math.min(averTheta, nextTheta - Math.PI / 10);
        } else if (averTheta < node.theta && j > 0) {
          var lastTheta = row[j - 1].theta;
          node.theta = Math.max(averTheta, lastTheta + Math.PI / 10);
        }
      }
    }
  }

  // Set locations
  for (var i = 0; i < this.row.length; i++) {
    for (var j = 0; j < this.row[i].length; j++) {
      var row = this.row[i];
      var node = row[j];
      var vertexBounds = this.getVertexBounds(node.cell);
      this.setVertexLocation(
        node.cell,
        this.centerX - vertexBounds.width / 2 + this.rowRadi[i] * Math.cos(node.theta),
        this.centerY - vertexBounds.height / 2 + this.rowRadi[i] * Math.sin(node.theta)
      );
    }
  }
};

/**
 * Function: calcRowDims
 *
 * Recursive function to calculate the dimensions of each row
 *
 * Parameters:
 *
 * row - Array of internal nodes, the children of which are to be processed.
 * rowNum - Integer indicating which row is being processed.
 */
mxRadialTreeLayout.prototype.calcRowDims = function (row, rowNum) {
  if (row == null || row.length == 0) {
    return;
  }

  // Place root's children proportionally around the first level
  this.rowMinX[rowNum] = this.centerX;
  this.rowMaxX[rowNum] = this.centerX;
  this.rowMinCenX[rowNum] = this.centerX;
  this.rowMaxCenX[rowNum] = this.centerX;
  this.row[rowNum] = [];

  var rowHasChildren = false;

  for (var i = 0; i < row.length; i++) {
    var child = row[i] != null ? row[i].child : null;

    while (child != null) {
      var cell = child.cell;
      var vertexBounds = this.getVertexBounds(cell);

      this.rowMinX[rowNum] = Math.min(vertexBounds.x, this.rowMinX[rowNum]);
      this.rowMaxX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width, this.rowMaxX[rowNum]);
      this.rowMinCenX[rowNum] = Math.min(vertexBounds.x + vertexBounds.width / 2, this.rowMinCenX[rowNum]);
      this.rowMaxCenX[rowNum] = Math.max(vertexBounds.x + vertexBounds.width / 2, this.rowMaxCenX[rowNum]);
      this.rowRadi[rowNum] = vertexBounds.y - this.getVertexBounds(this.root).y;

      if (child.child != null) {
        rowHasChildren = true;
      }

      this.row[rowNum].push(child);
      child = child.next;
    }
  }

  if (rowHasChildren) {
    this.calcRowDims(this.row[rowNum], rowNum + 1);
  }
};

/**
 * layout/mxFastOrganicLayout.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxFastOrganicLayout
 *
 * Extends <mxGraphLayout> to implement a fast organic layout algorithm.
 * The vertices need to be connected for this layout to work, vertices
 * with no connections are ignored.
 *
 * Example:
 *
 * (code)
 * var layout = new mxFastOrganicLayout(graph);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 * Constructor: mxCompactTreeLayout
 *
 * Constructs a new fast organic layout for the specified graph.
 */
function mxFastOrganicLayout(graph) {
  mxGraphLayout.call(this, graph);
}

/**
 * Extends mxGraphLayout.
 */
mxFastOrganicLayout.prototype = new mxGraphLayout();
mxFastOrganicLayout.prototype.constructor = mxFastOrganicLayout;

/**
 * Variable: useInputOrigin
 *
 * Specifies if the top left corner of the input cells should be the origin
 * of the layout result. Default is true.
 */
mxFastOrganicLayout.prototype.useInputOrigin = true;

/**
 * Variable: resetEdges
 *
 * Specifies if all edge points of traversed edges should be removed.
 * Default is true.
 */
mxFastOrganicLayout.prototype.resetEdges = true;

/**
 * Variable: disableEdgeStyle
 *
 * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
 * modified by the result. Default is true.
 */
mxFastOrganicLayout.prototype.disableEdgeStyle = true;

/**
 * Variable: forceConstant
 *
 * The force constant by which the attractive forces are divided and the
 * replusive forces are multiple by the square of. The value equates to the
 * average radius there is of free space around each node. Default is 50.
 */
mxFastOrganicLayout.prototype.forceConstant = 50;

/**
 * Variable: forceConstantSquared
 *
 * Cache of <forceConstant>^2 for performance.
 */
mxFastOrganicLayout.prototype.forceConstantSquared = 0;

/**
 * Variable: minDistanceLimit
 *
 * Minimal distance limit. Default is 2. Prevents of
 * dividing by zero.
 */
mxFastOrganicLayout.prototype.minDistanceLimit = 2;

/**
 * Variable: maxDistanceLimit
 *
 * Maximal distance limit. Default is 500. Prevents of
 * dividing by zero.
 */
mxFastOrganicLayout.prototype.maxDistanceLimit = 500;

/**
 * Variable: minDistanceLimitSquared
 *
 * Cached version of <minDistanceLimit> squared.
 */
mxFastOrganicLayout.prototype.minDistanceLimitSquared = 4;

/**
 * Variable: initialTemp
 *
 * Start value of temperature. Default is 200.
 */
mxFastOrganicLayout.prototype.initialTemp = 200;

/**
 * Variable: temperature
 *
 * Temperature to limit displacement at later stages of layout.
 */
mxFastOrganicLayout.prototype.temperature = 0;

/**
 * Variable: maxIterations
 *
 * Total number of iterations to run the layout though.
 */
mxFastOrganicLayout.prototype.maxIterations = 0;

/**
 * Variable: iteration
 *
 * Current iteration count.
 */
mxFastOrganicLayout.prototype.iteration = 0;

/**
 * Variable: vertexArray
 *
 * An array of all vertices to be laid out.
 */
mxFastOrganicLayout.prototype.vertexArray;

/**
 * Variable: dispX
 *
 * An array of locally stored X co-ordinate displacements for the vertices.
 */
mxFastOrganicLayout.prototype.dispX;

/**
 * Variable: dispY
 *
 * An array of locally stored Y co-ordinate displacements for the vertices.
 */
mxFastOrganicLayout.prototype.dispY;

/**
 * Variable: cellLocation
 *
 * An array of locally stored co-ordinate positions for the vertices.
 */
mxFastOrganicLayout.prototype.cellLocation;

/**
 * Variable: radius
 *
 * The approximate radius of each cell, nodes only.
 */
mxFastOrganicLayout.prototype.radius;

/**
 * Variable: radiusSquared
 *
 * The approximate radius squared of each cell, nodes only.
 */
mxFastOrganicLayout.prototype.radiusSquared;

/**
 * Variable: isMoveable
 *
 * Array of booleans representing the movable states of the vertices.
 */
mxFastOrganicLayout.prototype.isMoveable;

/**
 * Variable: neighbours
 *
 * Local copy of cell neighbours.
 */
mxFastOrganicLayout.prototype.neighbours;

/**
 * Variable: indices
 *
 * Hashtable from cells to local indices.
 */
mxFastOrganicLayout.prototype.indices;

/**
 * Variable: allowedToRun
 *
 * Boolean flag that specifies if the layout is allowed to run. If this is
 * set to false, then the layout exits in the following iteration.
 */
mxFastOrganicLayout.prototype.allowedToRun = true;

/**
 * Function: isVertexIgnored
 *
 * Returns a boolean indicating if the given <mxCell> should be ignored as a
 * vertex. This returns true if the cell has no connections.
 *
 * Parameters:
 *
 * vertex - <mxCell> whose ignored state should be returned.
 */
mxFastOrganicLayout.prototype.isVertexIgnored = function (vertex) {
  return (
    mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) || this.graph.getConnections(vertex).length == 0
  );
};

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>. This operates on all children of the
 * given parent where <isVertexIgnored> returns false.
 */
mxFastOrganicLayout.prototype.execute = function (parent) {
  var model = this.graph.getModel();
  this.vertexArray = [];
  var cells = this.graph.getChildVertices(parent);

  for (var i = 0; i < cells.length; i++) {
    if (!this.isVertexIgnored(cells[i])) {
      this.vertexArray.push(cells[i]);
    }
  }

  var initialBounds = this.useInputOrigin ? this.graph.getBoundingBoxFromGeometry(this.vertexArray) : null;
  var n = this.vertexArray.length;

  this.indices = [];
  this.dispX = [];
  this.dispY = [];
  this.cellLocation = [];
  this.isMoveable = [];
  this.neighbours = [];
  this.radius = [];
  this.radiusSquared = [];

  if (this.forceConstant < 0.001) {
    this.forceConstant = 0.001;
  }

  this.forceConstantSquared = this.forceConstant * this.forceConstant;

  // Create a map of vertices first. This is required for the array of
  // arrays called neighbours which holds, for each vertex, a list of
  // ints which represents the neighbours cells to that vertex as
  // the indices into vertexArray
  for (var i = 0; i < this.vertexArray.length; i++) {
    var vertex = this.vertexArray[i];
    this.cellLocation[i] = [];

    // Set up the mapping from array indices to cells
    var id = mxObjectIdentity.get(vertex);
    this.indices[id] = i;
    var bounds = this.getVertexBounds(vertex);

    // Set the X,Y value of the internal version of the cell to
    // the center point of the vertex for better positioning
    var width = bounds.width;
    var height = bounds.height;

    // Randomize (0, 0) locations
    var x = bounds.x;
    var y = bounds.y;

    this.cellLocation[i][0] = x + width / 2.0;
    this.cellLocation[i][1] = y + height / 2.0;
    this.radius[i] = Math.min(width, height);
    this.radiusSquared[i] = this.radius[i] * this.radius[i];
  }

  // Moves cell location back to top-left from center locations used in
  // algorithm, resetting the edge points is part of the transaction
  model.beginUpdate();
  try {
    for (var i = 0; i < n; i++) {
      this.dispX[i] = 0;
      this.dispY[i] = 0;
      this.isMoveable[i] = this.isVertexMovable(this.vertexArray[i]);

      // Get lists of neighbours to all vertices, translate the cells
      // obtained in indices into vertexArray and store as an array
      // against the orginial cell index
      var edges = this.graph.getConnections(this.vertexArray[i], parent);
      var cells = this.graph.getOpposites(edges, this.vertexArray[i]);
      this.neighbours[i] = [];

      for (var j = 0; j < cells.length; j++) {
        // Resets the points on the traversed edge
        if (this.resetEdges) {
          this.graph.resetEdge(edges[j]);
        }

        if (this.disableEdgeStyle) {
          this.setEdgeStyleEnabled(edges[j], false);
        }

        // Looks the cell up in the indices dictionary
        var id = mxObjectIdentity.get(cells[j]);
        var index = this.indices[id];

        // Check the connected cell in part of the vertex list to be
        // acted on by this layout
        if (index != null) {
          this.neighbours[i][j] = index;
        }

        // Else if index of the other cell doesn't correspond to
        // any cell listed to be acted upon in this layout. Set
        // the index to the value of this vertex (a dummy self-loop)
        // so the attraction force of the edge is not calculated
        else {
          this.neighbours[i][j] = i;
        }
      }
    }
    this.temperature = this.initialTemp;

    // If max number of iterations has not been set, guess it
    if (this.maxIterations == 0) {
      this.maxIterations = 20 * Math.sqrt(n);
    }

    // Main iteration loop
    for (this.iteration = 0; this.iteration < this.maxIterations; this.iteration++) {
      if (!this.allowedToRun) {
        return;
      }

      // Calculate repulsive forces on all vertices
      this.calcRepulsion();

      // Calculate attractive forces through edges
      this.calcAttraction();

      this.calcPositions();
      this.reduceTemperature();
    }

    var minx = null;
    var miny = null;

    for (var i = 0; i < this.vertexArray.length; i++) {
      var vertex = this.vertexArray[i];

      if (this.isVertexMovable(vertex)) {
        var bounds = this.getVertexBounds(vertex);

        if (bounds != null) {
          this.cellLocation[i][0] -= bounds.width / 2.0;
          this.cellLocation[i][1] -= bounds.height / 2.0;

          var x = this.graph.snap(Math.round(this.cellLocation[i][0]));
          var y = this.graph.snap(Math.round(this.cellLocation[i][1]));

          this.setVertexLocation(vertex, x, y);

          if (minx == null) {
            minx = x;
          } else {
            minx = Math.min(minx, x);
          }

          if (miny == null) {
            miny = y;
          } else {
            miny = Math.min(miny, y);
          }
        }
      }
    }

    // Modifies the cloned geometries in-place. Not needed
    // to clone the geometries again as we're in the same
    // undoable change.
    var dx = -(minx || 0) + 1;
    var dy = -(miny || 0) + 1;

    if (initialBounds != null) {
      dx += initialBounds.x;
      dy += initialBounds.y;
    }

    this.graph.moveCells(this.vertexArray, dx, dy);
  } finally {
    model.endUpdate();
  }
};

/**
 * Function: calcPositions
 *
 * Takes the displacements calculated for each cell and applies them to the
 * local cache of cell positions. Limits the displacement to the current
 * temperature.
 */
mxFastOrganicLayout.prototype.calcPositions = function () {
  for (var index = 0; index < this.vertexArray.length; index++) {
    if (this.isMoveable[index]) {
      // Get the distance of displacement for this node for this
      // iteration
      var deltaLength = Math.sqrt(this.dispX[index] * this.dispX[index] + this.dispY[index] * this.dispY[index]);

      if (deltaLength < 0.001) {
        deltaLength = 0.001;
      }

      // Scale down by the current temperature if less than the
      // displacement distance
      var newXDisp = (this.dispX[index] / deltaLength) * Math.min(deltaLength, this.temperature);

      var newYDisp = (this.dispY[index] / deltaLength) * Math.min(deltaLength, this.temperature);

      // reset displacements
      this.dispX[index] = 0;
      this.dispY[index] = 0;

      // Update the cached cell locations
      this.cellLocation[index][0] += newXDisp;
      this.cellLocation[index][1] += newYDisp;
    }
  }
};

/**
 * Function: calcAttraction
 *
 * Calculates the attractive forces between all laid out nodes linked by
 * edges
 */
mxFastOrganicLayout.prototype.calcAttraction = function () {
  // Check the neighbours of each vertex and calculate the attractive
  // force of the edge connecting them
  for (var i = 0; i < this.vertexArray.length; i++) {
    for (var k = 0; k < this.neighbours[i].length; k++) {
      // Get the index of the othe cell in the vertex array
      var j = this.neighbours[i][k];

      // Do not proceed self-loops
      if (i != j && this.isMoveable[i] && this.isMoveable[j]) {
        var xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];
        var yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];

        // The distance between the nodes
        var deltaLengthSquared = xDelta * xDelta + yDelta * yDelta - this.radiusSquared[i] - this.radiusSquared[j];

        if (deltaLengthSquared < this.minDistanceLimitSquared) {
          deltaLengthSquared = this.minDistanceLimitSquared;
        }

        var deltaLength = Math.sqrt(deltaLengthSquared);
        var force = deltaLengthSquared / this.forceConstant;

        var displacementX = (xDelta / deltaLength) * force;
        var displacementY = (yDelta / deltaLength) * force;

        this.dispX[i] -= displacementX;
        this.dispY[i] -= displacementY;

        this.dispX[j] += displacementX;
        this.dispY[j] += displacementY;
      }
    }
  }
};

/**
 * Function: calcRepulsion
 *
 * Calculates the repulsive forces between all laid out nodes
 */
mxFastOrganicLayout.prototype.calcRepulsion = function () {
  var vertexCount = this.vertexArray.length;

  for (var i = 0; i < vertexCount; i++) {
    for (var j = i; j < vertexCount; j++) {
      // Exits if the layout is no longer allowed to run
      if (!this.allowedToRun) {
        return;
      }

      if (j != i && this.isMoveable[i] && this.isMoveable[j]) {
        var xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];
        var yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];

        if (xDelta == 0) {
          xDelta = 0.01 + Math.random();
        }

        if (yDelta == 0) {
          yDelta = 0.01 + Math.random();
        }

        // Distance between nodes
        var deltaLength = Math.sqrt(xDelta * xDelta + yDelta * yDelta);
        var deltaLengthWithRadius = deltaLength - this.radius[i] - this.radius[j];

        if (deltaLengthWithRadius > this.maxDistanceLimit) {
          // Ignore vertices too far apart
          continue;
        }

        if (deltaLengthWithRadius < this.minDistanceLimit) {
          deltaLengthWithRadius = this.minDistanceLimit;
        }

        var force = this.forceConstantSquared / deltaLengthWithRadius;

        var displacementX = (xDelta / deltaLength) * force;
        var displacementY = (yDelta / deltaLength) * force;

        this.dispX[i] += displacementX;
        this.dispY[i] += displacementY;

        this.dispX[j] -= displacementX;
        this.dispY[j] -= displacementY;
      }
    }
  }
};

/**
 * Function: reduceTemperature
 *
 * Reduces the temperature of the layout from an initial setting in a linear
 * fashion to zero.
 */
mxFastOrganicLayout.prototype.reduceTemperature = function () {
  this.temperature = this.initialTemp * (1.0 - this.iteration / this.maxIterations);
};

/**
 * layout/mxCircleLayout.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCircleLayout
 *
 * Extends <mxGraphLayout> to implement a circluar layout for a given radius.
 * The vertices do not need to be connected for this layout to work and all
 * connections between vertices are not taken into account.
 *
 * Example:
 *
 * (code)
 * var layout = new mxCircleLayout(graph);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 * Constructor: mxCircleLayout
 *
 * Constructs a new circular layout for the specified radius.
 *
 * Arguments:
 *
 * graph - <mxGraph> that contains the cells.
 * radius - Optional radius as an int. Default is 100.
 */
function mxCircleLayout(graph, radius) {
  mxGraphLayout.call(this, graph);
  this.radius = radius != null ? radius : 100;
}

/**
 * Extends mxGraphLayout.
 */
mxCircleLayout.prototype = new mxGraphLayout();
mxCircleLayout.prototype.constructor = mxCircleLayout;

/**
 * Variable: radius
 *
 * Integer specifying the size of the radius. Default is 100.
 */
mxCircleLayout.prototype.radius = null;

/**
 * Variable: moveCircle
 *
 * Boolean specifying if the circle should be moved to the top,
 * left corner specified by <x0> and <y0>. Default is false.
 */
mxCircleLayout.prototype.moveCircle = false;

/**
 * Variable: x0
 *
 * Integer specifying the left coordinate of the circle.
 * Default is 0.
 */
mxCircleLayout.prototype.x0 = 0;

/**
 * Variable: y0
 *
 * Integer specifying the top coordinate of the circle.
 * Default is 0.
 */
mxCircleLayout.prototype.y0 = 0;

/**
 * Variable: resetEdges
 *
 * Specifies if all edge points of traversed edges should be removed.
 * Default is true.
 */
mxCircleLayout.prototype.resetEdges = true;

/**
 * Variable: disableEdgeStyle
 *
 * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
 * modified by the result. Default is true.
 */
mxCircleLayout.prototype.disableEdgeStyle = true;

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>.
 */
mxCircleLayout.prototype.execute = function (parent) {
  var model = this.graph.getModel();

  // Moves the vertices to build a circle. Makes sure the
  // radius is large enough for the vertices to not
  // overlap
  model.beginUpdate();
  try {
    // Gets all vertices inside the parent and finds
    // the maximum dimension of the largest vertex
    var max = 0;
    var top = null;
    var left = null;
    var vertices = [];
    var childCount = model.getChildCount(parent);

    for (var i = 0; i < childCount; i++) {
      var cell = model.getChildAt(parent, i);

      if (!this.isVertexIgnored(cell)) {
        vertices.push(cell);
        var bounds = this.getVertexBounds(cell);

        if (top == null) {
          top = bounds.y;
        } else {
          top = Math.min(top, bounds.y);
        }

        if (left == null) {
          left = bounds.x;
        } else {
          left = Math.min(left, bounds.x);
        }

        max = Math.max(max, Math.max(bounds.width, bounds.height));
      } else if (!this.isEdgeIgnored(cell)) {
        // Resets the points on the traversed edge
        if (this.resetEdges) {
          this.graph.resetEdge(cell);
        }

        if (this.disableEdgeStyle) {
          this.setEdgeStyleEnabled(cell, false);
        }
      }
    }

    var r = this.getRadius(vertices.length, max);

    // Moves the circle to the specified origin
    if (this.moveCircle) {
      left = this.x0;
      top = this.y0;
    }

    this.circle(vertices, r, left, top);
  } finally {
    model.endUpdate();
  }
};

/**
 * Function: getRadius
 *
 * Returns the radius to be used for the given vertex count. Max is the maximum
 * width or height of all vertices in the layout.
 */
mxCircleLayout.prototype.getRadius = function (count, max) {
  return Math.max((count * max) / Math.PI, this.radius);
};

/**
 * Function: circle
 *
 * Executes the circular layout for the specified array
 * of vertices and the given radius. This is called from
 * <execute>.
 */
mxCircleLayout.prototype.circle = function (vertices, r, left, top) {
  var vertexCount = vertices.length;
  var phi = (2 * Math.PI) / vertexCount;

  for (var i = 0; i < vertexCount; i++) {
    if (this.isVertexMovable(vertices[i])) {
      this.setVertexLocation(
        vertices[i],
        Math.round(left + r + r * Math.sin(i * phi)),
        Math.round(top + r + r * Math.cos(i * phi))
      );
    }
  }
};

/**
 * layout/mxParallelEdgeLayout.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxParallelEdgeLayout
 *
 * Extends <mxGraphLayout> for arranging parallel edges. This layout works
 * on edges for all pairs of vertices where there is more than one edge
 * connecting the latter.
 *
 * Example:
 *
 * (code)
 * var layout = new mxParallelEdgeLayout(graph);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 * To run the layout for the parallel edges of a changed edge only, the
 * following code can be used.
 *
 * (code)
 * var layout = new mxParallelEdgeLayout(graph);
 *
 * graph.addListener(mxEvent.CELL_CONNECTED, function(sender, evt)
 * {
 *   var model = graph.getModel();
 *   var edge = evt.getProperty('edge');
 *   var src = model.getTerminal(edge, true);
 *   var trg = model.getTerminal(edge, false);
 *
 *   layout.isEdgeIgnored = function(edge2)
 *   {
 *     var src2 = model.getTerminal(edge2, true);
 *     var trg2 = model.getTerminal(edge2, false);
 *
 *     return !(model.isEdge(edge2) && ((src == src2 && trg == trg2) || (src == trg2 && trg == src2)));
 *   };
 *
 *   layout.execute(graph.getDefaultParent());
 * });
 * (end)
 *
 * Constructor: mxParallelEdgeLayout
 *
 * Constructs a new parallel edge layout for the specified graph.
 */
function mxParallelEdgeLayout(graph) {
  mxGraphLayout.call(this, graph);
}

/**
 * Extends mxGraphLayout.
 */
mxParallelEdgeLayout.prototype = new mxGraphLayout();
mxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout;

/**
 * Variable: spacing
 *
 * Defines the spacing between the parallels. Default is 20.
 */
mxParallelEdgeLayout.prototype.spacing = 20;

/**
 * Variable: checkOverlap
 *
 * Specifies if only overlapping edges should be considered
 * parallel. Default is false.
 */
mxParallelEdgeLayout.prototype.checkOverlap = false;

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>.
 */
mxParallelEdgeLayout.prototype.execute = function (parent, cells) {
  var lookup = this.findParallels(parent, cells);

  this.graph.model.beginUpdate();
  try {
    for (var i in lookup) {
      var parallels = lookup[i];

      if (parallels.length > 1) {
        this.layout(parallels);
      }
    }
  } finally {
    this.graph.model.endUpdate();
  }
};

/**
 * Function: findParallels
 *
 * Finds the parallel edges in the given parent.
 */
mxParallelEdgeLayout.prototype.findParallels = function (parent, cells) {
  var lookup = [];

  var addCell = mxUtils.bind(this, function (cell) {
    if (!this.isEdgeIgnored(cell)) {
      var id = this.getEdgeId(cell);

      if (id != null) {
        if (lookup[id] == null) {
          lookup[id] = [];
        }

        lookup[id].push(cell);
      }
    }
  });

  if (cells != null) {
    for (var i = 0; i < cells.length; i++) {
      addCell(cells[i]);
    }
  } else {
    var model = this.graph.getModel();
    var childCount = model.getChildCount(parent);

    for (var i = 0; i < childCount; i++) {
      addCell(model.getChildAt(parent, i));
    }
  }

  return lookup;
};

/**
 * Function: getEdgeId
 *
 * Returns a unique ID for the given edge. The id is independent of the
 * edge direction and is built using the visible terminal of the given
 * edge.
 */
mxParallelEdgeLayout.prototype.getEdgeId = function (edge) {
  var view = this.graph.getView();

  // Cannot used cached visible terminal because this could be triggered in BEFORE_UNDO
  var src = view.getVisibleTerminal(edge, true);
  var trg = view.getVisibleTerminal(edge, false);
  var pts = '';

  if (src != null && trg != null) {
    src = mxObjectIdentity.get(src);
    trg = mxObjectIdentity.get(trg);

    if (this.checkOverlap) {
      var state = this.graph.view.getState(edge);

      if (state != null && state.absolutePoints != null) {
        var tmp = [];

        for (var i = 0; i < state.absolutePoints.length; i++) {
          var pt = state.absolutePoints[i];

          if (pt != null) {
            tmp.push(pt.x, pt.y);
          }
        }

        pts = tmp.join(',');
      }
    }

    return (src > trg ? trg + '-' + src : src + '-' + trg) + pts;
  }

  return null;
};

/**
 * Function: layout
 *
 * Lays out the parallel edges in the given array.
 */
mxParallelEdgeLayout.prototype.layout = function (parallels) {
  var edge = parallels[0];
  var view = this.graph.getView();
  var model = this.graph.getModel();
  var src = model.getGeometry(view.getVisibleTerminal(edge, true));
  var trg = model.getGeometry(view.getVisibleTerminal(edge, false));

  // Routes multiple loops
  if (src == trg) {
    var x0 = src.x + src.width + this.spacing;
    var y0 = src.y + src.height / 2;

    for (var i = 0; i < parallels.length; i++) {
      this.route(parallels[i], x0, y0);
      x0 += this.spacing;
    }
  } else if (src != null && trg != null) {
    // Routes parallel edges
    var scx = src.x + src.width / 2;
    var scy = src.y + src.height / 2;

    var tcx = trg.x + trg.width / 2;
    var tcy = trg.y + trg.height / 2;

    var dx = tcx - scx;
    var dy = tcy - scy;

    var len = Math.sqrt(dx * dx + dy * dy);

    if (len > 0) {
      var x0 = scx + dx / 2;
      var y0 = scy + dy / 2;

      var nx = (dy * this.spacing) / len;
      var ny = (dx * this.spacing) / len;

      x0 += (nx * (parallels.length - 1)) / 2;
      y0 -= (ny * (parallels.length - 1)) / 2;

      for (var i = 0; i < parallels.length; i++) {
        this.route(parallels[i], x0, y0);
        x0 -= nx;
        y0 += ny;
      }
    }
  }
};

/**
 * Function: route
 *
 * Routes the given edge via the given point.
 */
mxParallelEdgeLayout.prototype.route = function (edge, x, y) {
  if (this.graph.isCellMovable(edge)) {
    this.setEdgePoints(edge, [new mxPoint(x, y)]);
  }
};

/**
 * layout/mxCompositeLayout.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCompositeLayout
 *
 * Allows to compose multiple layouts into a single layout. The master layout
 * is the layout that handles move operations if another layout than the first
 * element in <layouts> should be used. The <master> layout is not executed as
 * the code assumes that it is part of <layouts>.
 *
 * Example:
 * (code)
 * var first = new mxFastOrganicLayout(graph);
 * var second = new mxParallelEdgeLayout(graph);
 * var layout = new mxCompositeLayout(graph, [first, second], first);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 * Constructor: mxCompositeLayout
 *
 * Constructs a new layout using the given layouts. The graph instance is
 * required for creating the transaction that contains all layouts.
 *
 * Arguments:
 *
 * graph - Reference to the enclosing <mxGraph>.
 * layouts - Array of <mxGraphLayouts>.
 * master - Optional layout that handles moves. If no layout is given then
 * the first layout of the above array is used to handle moves.
 */
function mxCompositeLayout(graph, layouts, master) {
  mxGraphLayout.call(this, graph);
  this.layouts = layouts;
  this.master = master;
}

/**
 * Extends mxGraphLayout.
 */
mxCompositeLayout.prototype = new mxGraphLayout();
mxCompositeLayout.prototype.constructor = mxCompositeLayout;

/**
 * Variable: layouts
 *
 * Holds the array of <mxGraphLayouts> that this layout contains.
 */
mxCompositeLayout.prototype.layouts = null;

/**
 * Variable: master
 *
 * Reference to the <mxGraphLayouts> that handles moves. If this is null
 * then the first layout in <layouts> is used.
 */
mxCompositeLayout.prototype.master = null;

/**
 * Function: moveCell
 *
 * Implements <mxGraphLayout.moveCell> by calling move on <master> or the first
 * layout in <layouts>.
 */
mxCompositeLayout.prototype.moveCell = function (cell, x, y) {
  if (this.master != null) {
    this.master.moveCell.apply(this.master, arguments);
  } else {
    this.layouts[0].moveCell.apply(this.layouts[0], arguments);
  }
};

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute> by executing all <layouts> in a
 * single transaction.
 */
mxCompositeLayout.prototype.execute = function (parent) {
  var model = this.graph.getModel();

  model.beginUpdate();
  try {
    for (var i = 0; i < this.layouts.length; i++) {
      this.layouts[i].execute.apply(this.layouts[i], arguments);
    }
  } finally {
    model.endUpdate();
  }
};

/**
 * layout/mxEdgeLabelLayout.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxEdgeLabelLayout
 *
 * Extends <mxGraphLayout> to implement an edge label layout. This layout
 * makes use of cell states, which means the graph must be validated in
 * a graph view (so that the label bounds are available) before this layout
 * can be executed.
 *
 * Example:
 *
 * (code)
 * var layout = new mxEdgeLabelLayout(graph);
 * layout.execute(graph.getDefaultParent());
 * (end)
 *
 * Constructor: mxEdgeLabelLayout
 *
 * Constructs a new edge label layout.
 *
 * Arguments:
 *
 * graph - <mxGraph> that contains the cells.
 */
function mxEdgeLabelLayout(graph, radius) {
  mxGraphLayout.call(this, graph);
}

/**
 * Extends mxGraphLayout.
 */
mxEdgeLabelLayout.prototype = new mxGraphLayout();
mxEdgeLabelLayout.prototype.constructor = mxEdgeLabelLayout;

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>.
 */
mxEdgeLabelLayout.prototype.execute = function (parent) {
  var view = this.graph.view;
  var model = this.graph.getModel();

  // Gets all vertices and edges inside the parent
  var edges = [];
  var vertices = [];
  var childCount = model.getChildCount(parent);

  for (var i = 0; i < childCount; i++) {
    var cell = model.getChildAt(parent, i);
    var state = view.getState(cell);

    if (state != null) {
      if (!this.isVertexIgnored(cell)) {
        vertices.push(state);
      } else if (!this.isEdgeIgnored(cell)) {
        edges.push(state);
      }
    }
  }

  this.placeLabels(vertices, edges);
};

/**
 * Function: placeLabels
 *
 * Places the labels of the given edges.
 */
mxEdgeLabelLayout.prototype.placeLabels = function (v, e) {
  var model = this.graph.getModel();

  // Moves the vertices to build a circle. Makes sure the
  // radius is large enough for the vertices to not
  // overlap
  model.beginUpdate();
  try {
    for (var i = 0; i < e.length; i++) {
      var edge = e[i];

      if (edge != null && edge.text != null && edge.text.boundingBox != null) {
        for (var j = 0; j < v.length; j++) {
          var vertex = v[j];

          if (vertex != null) {
            this.avoid(edge, vertex);
          }
        }
      }
    }
  } finally {
    model.endUpdate();
  }
};

/**
 * Function: avoid
 *
 * Places the labels of the given edges.
 */
mxEdgeLabelLayout.prototype.avoid = function (edge, vertex) {
  var model = this.graph.getModel();
  var labRect = edge.text.boundingBox;

  if (mxUtils.intersects(labRect, vertex)) {
    var dy1 = -labRect.y - labRect.height + vertex.y;
    var dy2 = -labRect.y + vertex.y + vertex.height;

    var dy = Math.abs(dy1) < Math.abs(dy2) ? dy1 : dy2;

    var dx1 = -labRect.x - labRect.width + vertex.x;
    var dx2 = -labRect.x + vertex.x + vertex.width;

    var dx = Math.abs(dx1) < Math.abs(dx2) ? dx1 : dx2;

    if (Math.abs(dx) < Math.abs(dy)) {
      dy = 0;
    } else {
      dx = 0;
    }

    var g = model.getGeometry(edge.cell);

    if (g != null) {
      g = g.clone();

      if (g.offset != null) {
        g.offset.x += dx;
        g.offset.y += dy;
      } else {
        g.offset = new mxPoint(dx, dy);
      }

      model.setGeometry(edge.cell, g);
    }
  }
};

/**
 * layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGraphAbstractHierarchyCell
 *
 * An abstraction of an internal hierarchy node or edge
 *
 * Constructor: mxGraphAbstractHierarchyCell
 *
 * Constructs a new hierarchical layout algorithm.
 */
function mxGraphAbstractHierarchyCell() {
  this.x = [];
  this.y = [];
  this.temp = [];
}

/**
 * Variable: maxRank
 *
 * The maximum rank this cell occupies. Default is -1.
 */
mxGraphAbstractHierarchyCell.prototype.maxRank = -1;

/**
 * Variable: minRank
 *
 * The minimum rank this cell occupies. Default is -1.
 */
mxGraphAbstractHierarchyCell.prototype.minRank = -1;

/**
 * Variable: x
 *
 * The x position of this cell for each layer it occupies
 */
mxGraphAbstractHierarchyCell.prototype.x = null;

/**
 * Variable: y
 *
 * The y position of this cell for each layer it occupies
 */
mxGraphAbstractHierarchyCell.prototype.y = null;

/**
 * Variable: width
 *
 * The width of this cell. Default is 0.
 */
mxGraphAbstractHierarchyCell.prototype.width = 0;

/**
 * Variable: height
 *
 * The height of this cell. Default is 0.
 */
mxGraphAbstractHierarchyCell.prototype.height = 0;

/**
 * Variable: nextLayerConnectedCells
 *
 * A cached version of the cells this cell connects to on the next layer up
 */
mxGraphAbstractHierarchyCell.prototype.nextLayerConnectedCells = null;

/**
 * Variable: previousLayerConnectedCells
 *
 * A cached version of the cells this cell connects to on the next layer down
 */
mxGraphAbstractHierarchyCell.prototype.previousLayerConnectedCells = null;

/**
 * Variable: temp
 *
 * Temporary variable for general use. Generally, try to avoid
 * carrying information between stages. Currently, the longest
 * path layering sets temp to the rank position in fixRanks()
 * and the crossing reduction uses this. This meant temp couldn't
 * be used for hashing the nodes in the model dfs and so hashCode
 * was created
 */
mxGraphAbstractHierarchyCell.prototype.temp = null;

/**
 * Function: getNextLayerConnectedCells
 *
 * Returns the cells this cell connects to on the next layer up
 */
mxGraphAbstractHierarchyCell.prototype.getNextLayerConnectedCells = function (layer) {
  return null;
};

/**
 * Function: getPreviousLayerConnectedCells
 *
 * Returns the cells this cell connects to on the next layer down
 */
mxGraphAbstractHierarchyCell.prototype.getPreviousLayerConnectedCells = function (layer) {
  return null;
};

/**
 * Function: isEdge
 *
 * Returns whether or not this cell is an edge
 */
mxGraphAbstractHierarchyCell.prototype.isEdge = function () {
  return false;
};

/**
 * Function: isVertex
 *
 * Returns whether or not this cell is a node
 */
mxGraphAbstractHierarchyCell.prototype.isVertex = function () {
  return false;
};

/**
 * Function: getGeneralPurposeVariable
 *
 * Gets the value of temp for the specified layer
 */
mxGraphAbstractHierarchyCell.prototype.getGeneralPurposeVariable = function (layer) {
  return null;
};

/**
 * Function: setGeneralPurposeVariable
 *
 * Set the value of temp for the specified layer
 */
mxGraphAbstractHierarchyCell.prototype.setGeneralPurposeVariable = function (layer, value) {
  return null;
};

/**
 * Function: setX
 *
 * Set the value of x for the specified layer
 */
mxGraphAbstractHierarchyCell.prototype.setX = function (layer, value) {
  if (this.isVertex()) {
    this.x[0] = value;
  } else if (this.isEdge()) {
    this.x[layer - this.minRank - 1] = value;
  }
};

/**
 * Function: getX
 *
 * Gets the value of x on the specified layer
 */
mxGraphAbstractHierarchyCell.prototype.getX = function (layer) {
  if (this.isVertex()) {
    return this.x[0];
  } else if (this.isEdge()) {
    return this.x[layer - this.minRank - 1];
  }

  return 0.0;
};

/**
 * Function: setY
 *
 * Set the value of y for the specified layer
 */
mxGraphAbstractHierarchyCell.prototype.setY = function (layer, value) {
  if (this.isVertex()) {
    this.y[0] = value;
  } else if (this.isEdge()) {
    this.y[layer - this.minRank - 1] = value;
  }
};

/**
 * layout/hierarchical/model/mxGraphHierarchyNode.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGraphHierarchyNode
 *
 * An abstraction of a hierarchical edge for the hierarchy layout
 *
 * Constructor: mxGraphHierarchyNode
 *
 * Constructs an internal node to represent the specified real graph cell
 *
 * Arguments:
 *
 * cell - the real graph cell this node represents
 */
function mxGraphHierarchyNode(cell) {
  mxGraphAbstractHierarchyCell.apply(this, arguments);
  this.cell = cell;
  this.id = mxObjectIdentity.get(cell);
  this.connectsAsTarget = [];
  this.connectsAsSource = [];
}

/**
 * Extends mxGraphAbstractHierarchyCell.
 */
mxGraphHierarchyNode.prototype = new mxGraphAbstractHierarchyCell();
mxGraphHierarchyNode.prototype.constructor = mxGraphHierarchyNode;

/**
 * Variable: cell
 *
 * The graph cell this object represents.
 */
mxGraphHierarchyNode.prototype.cell = null;

/**
 * Variable: id
 *
 * The object identity of the wrapped cell
 */
mxGraphHierarchyNode.prototype.id = null;

/**
 * Variable: connectsAsTarget
 *
 * Collection of hierarchy edges that have this node as a target
 */
mxGraphHierarchyNode.prototype.connectsAsTarget = null;

/**
 * Variable: connectsAsSource
 *
 * Collection of hierarchy edges that have this node as a source
 */
mxGraphHierarchyNode.prototype.connectsAsSource = null;

/**
 * Variable: hashCode
 *
 * Assigns a unique hashcode for each node. Used by the model dfs instead
 * of copying HashSets
 */
mxGraphHierarchyNode.prototype.hashCode = false;

/**
 * Function: getRankValue
 *
 * Returns the integer value of the layer that this node resides in
 */
mxGraphHierarchyNode.prototype.getRankValue = function (layer) {
  return this.maxRank;
};

/**
 * Function: getNextLayerConnectedCells
 *
 * Returns the cells this cell connects to on the next layer up
 */
mxGraphHierarchyNode.prototype.getNextLayerConnectedCells = function (layer) {
  if (this.nextLayerConnectedCells == null) {
    this.nextLayerConnectedCells = [];
    this.nextLayerConnectedCells[0] = [];

    for (var i = 0; i < this.connectsAsTarget.length; i++) {
      var edge = this.connectsAsTarget[i];

      if (edge.maxRank == -1 || edge.maxRank == layer + 1) {
        // Either edge is not in any rank or
        // no dummy nodes in edge, add node of other side of edge
        this.nextLayerConnectedCells[0].push(edge.source);
      } else {
        // Edge spans at least two layers, add edge
        this.nextLayerConnectedCells[0].push(edge);
      }
    }
  }

  return this.nextLayerConnectedCells[0];
};

/**
 * Function: getPreviousLayerConnectedCells
 *
 * Returns the cells this cell connects to on the next layer down
 */
mxGraphHierarchyNode.prototype.getPreviousLayerConnectedCells = function (layer) {
  if (this.previousLayerConnectedCells == null) {
    this.previousLayerConnectedCells = [];
    this.previousLayerConnectedCells[0] = [];

    for (var i = 0; i < this.connectsAsSource.length; i++) {
      var edge = this.connectsAsSource[i];

      if (edge.minRank == -1 || edge.minRank == layer - 1) {
        // No dummy nodes in edge, add node of other side of edge
        this.previousLayerConnectedCells[0].push(edge.target);
      } else {
        // Edge spans at least two layers, add edge
        this.previousLayerConnectedCells[0].push(edge);
      }
    }
  }

  return this.previousLayerConnectedCells[0];
};

/**
 * Function: isVertex
 *
 * Returns true.
 */
mxGraphHierarchyNode.prototype.isVertex = function () {
  return true;
};

/**
 * Function: getGeneralPurposeVariable
 *
 * Gets the value of temp for the specified layer
 */
mxGraphHierarchyNode.prototype.getGeneralPurposeVariable = function (layer) {
  return this.temp[0];
};

/**
 * Function: setGeneralPurposeVariable
 *
 * Set the value of temp for the specified layer
 */
mxGraphHierarchyNode.prototype.setGeneralPurposeVariable = function (layer, value) {
  this.temp[0] = value;
};

/**
 * Function: isAncestor
 */
mxGraphHierarchyNode.prototype.isAncestor = function (otherNode) {
  // Firstly, the hash code of this node needs to be shorter than the
  // other node
  if (
    otherNode != null &&
    this.hashCode != null &&
    otherNode.hashCode != null &&
    this.hashCode.length < otherNode.hashCode.length
  ) {
    if (this.hashCode == otherNode.hashCode) {
      return true;
    }

    if (this.hashCode == null || this.hashCode == null) {
      return false;
    }

    // Secondly, this hash code must match the start of the other
    // node's hash code. Arrays.equals cannot be used here since
    // the arrays are different length, and we do not want to
    // perform another array copy.
    for (var i = 0; i < this.hashCode.length; i++) {
      if (this.hashCode[i] != otherNode.hashCode[i]) {
        return false;
      }
    }

    return true;
  }

  return false;
};

/**
 * Function: getCoreCell
 *
 * Gets the core vertex associated with this wrapper
 */
mxGraphHierarchyNode.prototype.getCoreCell = function () {
  return this.cell;
};

/**
 * layout/hierarchical/model/mxGraphHierarchyEdge.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGraphHierarchyEdge
 *
 * An abstraction of a hierarchical edge for the hierarchy layout
 *
 * Constructor: mxGraphHierarchyEdge
 *
 * Constructs a hierarchy edge
 *
 * Arguments:
 *
 * edges - a list of real graph edges this abstraction represents
 */
function mxGraphHierarchyEdge(edges) {
  mxGraphAbstractHierarchyCell.apply(this, arguments);
  this.edges = edges;
  this.ids = [];

  for (var i = 0; i < edges.length; i++) {
    this.ids.push(mxObjectIdentity.get(edges[i]));
  }
}

/**
 * Extends mxGraphAbstractHierarchyCell.
 */
mxGraphHierarchyEdge.prototype = new mxGraphAbstractHierarchyCell();
mxGraphHierarchyEdge.prototype.constructor = mxGraphHierarchyEdge;

/**
 * Variable: edges
 *
 * The graph edge(s) this object represents. Parallel edges are all grouped
 * together within one hierarchy edge.
 */
mxGraphHierarchyEdge.prototype.edges = null;

/**
 * Variable: ids
 *
 * The object identities of the wrapped cells
 */
mxGraphHierarchyEdge.prototype.ids = null;

/**
 * Variable: source
 *
 * The node this edge is sourced at
 */
mxGraphHierarchyEdge.prototype.source = null;

/**
 * Variable: target
 *
 * The node this edge targets
 */
mxGraphHierarchyEdge.prototype.target = null;

/**
 * Variable: isReversed
 *
 * Whether or not the direction of this edge has been reversed
 * internally to create a DAG for the hierarchical layout
 */
mxGraphHierarchyEdge.prototype.isReversed = false;

/**
 * Function: invert
 *
 * Inverts the direction of this internal edge(s)
 */
mxGraphHierarchyEdge.prototype.invert = function (layer) {
  var temp = this.source;
  this.source = this.target;
  this.target = temp;
  this.isReversed = !this.isReversed;
};

/**
 * Function: getNextLayerConnectedCells
 *
 * Returns the cells this cell connects to on the next layer up
 */
mxGraphHierarchyEdge.prototype.getNextLayerConnectedCells = function (layer) {
  if (this.nextLayerConnectedCells == null) {
    this.nextLayerConnectedCells = [];

    for (var i = 0; i < this.temp.length; i++) {
      this.nextLayerConnectedCells[i] = [];

      if (i == this.temp.length - 1) {
        this.nextLayerConnectedCells[i].push(this.source);
      } else {
        this.nextLayerConnectedCells[i].push(this);
      }
    }
  }

  return this.nextLayerConnectedCells[layer - this.minRank - 1];
};

/**
 * Function: getPreviousLayerConnectedCells
 *
 * Returns the cells this cell connects to on the next layer down
 */
mxGraphHierarchyEdge.prototype.getPreviousLayerConnectedCells = function (layer) {
  if (this.previousLayerConnectedCells == null) {
    this.previousLayerConnectedCells = [];

    for (var i = 0; i < this.temp.length; i++) {
      this.previousLayerConnectedCells[i] = [];

      if (i == 0) {
        this.previousLayerConnectedCells[i].push(this.target);
      } else {
        this.previousLayerConnectedCells[i].push(this);
      }
    }
  }

  return this.previousLayerConnectedCells[layer - this.minRank - 1];
};

/**
 * Function: isEdge
 *
 * Returns true.
 */
mxGraphHierarchyEdge.prototype.isEdge = function () {
  return true;
};

/**
 * Function: getGeneralPurposeVariable
 *
 * Gets the value of temp for the specified layer
 */
mxGraphHierarchyEdge.prototype.getGeneralPurposeVariable = function (layer) {
  return this.temp[layer - this.minRank - 1];
};

/**
 * Function: setGeneralPurposeVariable
 *
 * Set the value of temp for the specified layer
 */
mxGraphHierarchyEdge.prototype.setGeneralPurposeVariable = function (layer, value) {
  this.temp[layer - this.minRank - 1] = value;
};

/**
 * Function: getCoreCell
 *
 * Gets the first core edge associated with this wrapper
 */
mxGraphHierarchyEdge.prototype.getCoreCell = function () {
  if (this.edges != null && this.edges.length > 0) {
    return this.edges[0];
  }

  return null;
};

/**
 * layout/hierarchical/model/mxGraphHierarchyModel.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGraphHierarchyModel
 *
 * Internal model of a hierarchical graph. This model stores nodes and edges
 * equivalent to the real graph nodes and edges, but also stores the rank of the
 * cells, the order within the ranks and the new candidate locations of cells.
 * The internal model also reverses edge direction were appropriate , ignores
 * self-loop and groups parallels together under one edge object.
 *
 * Constructor: mxGraphHierarchyModel
 *
 * Creates an internal ordered graph model using the vertices passed in. If
 * there are any, leftward edge need to be inverted in the internal model
 *
 * Arguments:
 *
 * graph - the facade describing the graph to be operated on
 * vertices - the vertices for this hierarchy
 * ordered - whether or not the vertices are already ordered
 * deterministic - whether or not this layout should be deterministic on each
 * tightenToSource - whether or not to tighten vertices towards the sources
 * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
 * usage
 */
function mxGraphHierarchyModel(layout, vertices, roots, parent, tightenToSource) {
  var graph = layout.getGraph();
  this.tightenToSource = tightenToSource;
  this.roots = roots;
  this.parent = parent;

  // map of cells to internal cell needed for second run through
  // to setup the sink of edges correctly
  this.vertexMapper = new mxDictionary();
  this.edgeMapper = new mxDictionary();
  this.maxRank = 0;
  var internalVertices = [];

  if (vertices == null) {
    vertices = this.graph.getChildVertices(parent);
  }

  this.maxRank = this.SOURCESCANSTARTRANK;
  // map of cells to internal cell needed for second run through
  // to setup the sink of edges correctly. Guess size by number
  // of edges is roughly same as number of vertices.
  this.createInternalCells(layout, vertices, internalVertices);

  // Go through edges set their sink values. Also check the
  // ordering if and invert edges if necessary
  for (var i = 0; i < vertices.length; i++) {
    var edges = internalVertices[i].connectsAsSource;

    for (var j = 0; j < edges.length; j++) {
      var internalEdge = edges[j];
      var realEdges = internalEdge.edges;

      // Only need to process the first real edge, since
      // all the edges connect to the same other vertex
      if (realEdges != null && realEdges.length > 0) {
        var realEdge = realEdges[0];
        var targetCell = layout.getVisibleTerminal(realEdge, false);
        var internalTargetCell = this.vertexMapper.get(targetCell);

        if (internalVertices[i] == internalTargetCell) {
          // If there are parallel edges going between two vertices and not all are in the same direction
          // you can have navigated across one direction when doing the cycle reversal that isn't the same
          // direction as the first real edge in the array above. When that happens the if above catches
          // that and we correct the target cell before continuing.
          // This branch only detects this single case
          targetCell = layout.getVisibleTerminal(realEdge, true);
          internalTargetCell = this.vertexMapper.get(targetCell);
        }

        if (internalTargetCell != null && internalVertices[i] != internalTargetCell) {
          internalEdge.target = internalTargetCell;

          if (internalTargetCell.connectsAsTarget.length == 0) {
            internalTargetCell.connectsAsTarget = [];
          }

          if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0) {
            internalTargetCell.connectsAsTarget.push(internalEdge);
          }
        }
      }
    }

    // Use the temp variable in the internal nodes to mark this
    // internal vertex as having been visited.
    internalVertices[i].temp[0] = 1;
  }
}

/**
 * Variable: maxRank
 *
 * Stores the largest rank number allocated
 */
mxGraphHierarchyModel.prototype.maxRank = null;

/**
 * Variable: vertexMapper
 *
 * Map from graph vertices to internal model nodes.
 */
mxGraphHierarchyModel.prototype.vertexMapper = null;

/**
 * Variable: edgeMapper
 *
 * Map from graph edges to internal model edges
 */
mxGraphHierarchyModel.prototype.edgeMapper = null;

/**
 * Variable: ranks
 *
 * Mapping from rank number to actual rank
 */
mxGraphHierarchyModel.prototype.ranks = null;

/**
 * Variable: roots
 *
 * Store of roots of this hierarchy model, these are real graph cells, not
 * internal cells
 */
mxGraphHierarchyModel.prototype.roots = null;

/**
 * Variable: parent
 *
 * The parent cell whose children are being laid out
 */
mxGraphHierarchyModel.prototype.parent = null;

/**
 * Variable: dfsCount
 *
 * Count of the number of times the ancestor dfs has been used.
 */
mxGraphHierarchyModel.prototype.dfsCount = 0;

/**
 * Variable: SOURCESCANSTARTRANK
 *
 * High value to start source layering scan rank value from.
 */
mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK = 100000000;

/**
 * Variable: tightenToSource
 *
 * Whether or not to tighten the assigned ranks of vertices up towards
 * the source cells.
 */
mxGraphHierarchyModel.prototype.tightenToSource = false;

/**
 * Function: createInternalCells
 *
 * Creates all edges in the internal model
 *
 * Parameters:
 *
 * layout - Reference to the <mxHierarchicalLayout> algorithm.
 * vertices - Array of <mxCells> that represent the vertices whom are to
 * have an internal representation created.
 * internalVertices - The array of <mxGraphHierarchyNodes> to have their
 * information filled in using the real vertices.
 */
mxGraphHierarchyModel.prototype.createInternalCells = function (layout, vertices, internalVertices) {
  var graph = layout.getGraph();

  // Create internal edges
  for (var i = 0; i < vertices.length; i++) {
    internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
    this.vertexMapper.put(vertices[i], internalVertices[i]);

    // If the layout is deterministic, order the cells
    //List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
    var conns = layout.getEdges(vertices[i]);
    internalVertices[i].connectsAsSource = [];

    // Create internal edges, but don't do any rank assignment yet
    // First use the information from the greedy cycle remover to
    // invert the leftward edges internally
    for (var j = 0; j < conns.length; j++) {
      var cell = layout.getVisibleTerminal(conns[j], false);

      // Looking for outgoing edges only
      if (cell != vertices[i] && layout.graph.model.isVertex(cell) && !layout.isVertexIgnored(cell)) {
        // We process all edge between this source and its targets
        // If there are edges going both ways, we need to collect
        // them all into one internal edges to avoid looping problems
        // later. We assume this direction (source -> target) is the
        // natural direction if at least half the edges are going in
        // that direction.

        // The check below for edges[0] being in the vertex mapper is
        // in case we've processed this the other way around
        // (target -> source) and the number of edges in each direction
        // are the same. All the graph edges will have been assigned to
        // an internal edge going the other way, so we don't want to
        // process them again
        var undirectedEdges = layout.getEdgesBetween(vertices[i], cell, false);
        var directedEdges = layout.getEdgesBetween(vertices[i], cell, true);

        if (
          undirectedEdges != null &&
          undirectedEdges.length > 0 &&
          this.edgeMapper.get(undirectedEdges[0]) == null &&
          directedEdges.length * 2 >= undirectedEdges.length
        ) {
          var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);

          for (var k = 0; k < undirectedEdges.length; k++) {
            var edge = undirectedEdges[k];
            this.edgeMapper.put(edge, internalEdge);

            // Resets all point on the edge and disables the edge style
            // without deleting it from the cell style
            graph.resetEdge(edge);

            if (layout.disableEdgeStyle) {
              layout.setEdgeStyleEnabled(edge, false);
              layout.setOrthogonalEdge(edge, true);
            }
          }

          internalEdge.source = internalVertices[i];

          if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0) {
            internalVertices[i].connectsAsSource.push(internalEdge);
          }
        }
      }
    }

    // Ensure temp variable is cleared from any previous use
    internalVertices[i].temp[0] = 0;
  }
};

/**
 * Function: initialRank
 *
 * Basic determination of minimum layer ranking by working from from sources
 * or sinks and working through each node in the relevant edge direction.
 * Starting at the sinks is basically a longest path layering algorithm.
 */
mxGraphHierarchyModel.prototype.initialRank = function () {
  var startNodes = [];

  if (this.roots != null) {
    for (var i = 0; i < this.roots.length; i++) {
      var internalNode = this.vertexMapper.get(this.roots[i]);

      if (internalNode != null) {
        startNodes.push(internalNode);
      }
    }
  }

  var internalNodes = this.vertexMapper.getValues();

  for (var i = 0; i < internalNodes.length; i++) {
    // Mark the node as not having had a layer assigned
    internalNodes[i].temp[0] = -1;
  }

  var startNodesCopy = startNodes.slice();

  while (startNodes.length > 0) {
    var internalNode = startNodes[0];
    var layerDeterminingEdges;
    var edgesToBeMarked;

    layerDeterminingEdges = internalNode.connectsAsTarget;
    edgesToBeMarked = internalNode.connectsAsSource;

    // flag to keep track of whether or not all layer determining
    // edges have been scanned
    var allEdgesScanned = true;

    // Work out the layer of this node from the layer determining
    // edges. The minimum layer number of any node connected by one of
    // the layer determining edges variable
    var minimumLayer = this.SOURCESCANSTARTRANK;

    for (var i = 0; i < layerDeterminingEdges.length; i++) {
      var internalEdge = layerDeterminingEdges[i];

      if (internalEdge.temp[0] == 5270620) {
        // This edge has been scanned, get the layer of the
        // node on the other end
        var otherNode = internalEdge.source;
        minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
      } else {
        allEdgesScanned = false;

        break;
      }
    }

    // If all edge have been scanned, assign the layer, mark all
    // edges in the other direction and remove from the nodes list
    if (allEdgesScanned) {
      internalNode.temp[0] = minimumLayer;
      this.maxRank = Math.min(this.maxRank, minimumLayer);

      if (edgesToBeMarked != null) {
        for (var i = 0; i < edgesToBeMarked.length; i++) {
          var internalEdge = edgesToBeMarked[i];

          // Assign unique stamp ( y/m/d/h )
          internalEdge.temp[0] = 5270620;

          // Add node on other end of edge to LinkedList of
          // nodes to be analysed
          var otherNode = internalEdge.target;

          // Only add node if it hasn't been assigned a layer
          if (otherNode.temp[0] == -1) {
            startNodes.push(otherNode);

            // Mark this other node as neither being
            // unassigned nor assigned so it isn't
            // added to this list again, but it's
            // layer isn't used in any calculation.
            otherNode.temp[0] = -2;
          }
        }
      }

      startNodes.shift();
    } else {
      // Not all the edges have been scanned, get to the back of
      // the class and put the dunces cap on
      var removedCell = startNodes.shift();
      startNodes.push(internalNode);

      if (removedCell == internalNode && startNodes.length == 1) {
        // This is an error condition, we can't get out of
        // this loop. It could happen for more than one node
        // but that's a lot harder to detect. Log the error
        // TODO make log comment
        break;
      }
    }
  }

  // Normalize the ranks down from their large starting value to place
  // at least 1 sink on layer 0
  for (var i = 0; i < internalNodes.length; i++) {
    // Mark the node as not having had a layer assigned
    internalNodes[i].temp[0] -= this.maxRank;
  }

  // Tighten the rank 0 nodes as far as possible
  for (var i = 0; i < startNodesCopy.length; i++) {
    var internalNode = startNodesCopy[i];
    var currentMaxLayer = 0;
    var layerDeterminingEdges = internalNode.connectsAsSource;

    for (var j = 0; j < layerDeterminingEdges.length; j++) {
      var internalEdge = layerDeterminingEdges[j];
      var otherNode = internalEdge.target;
      internalNode.temp[0] = Math.max(currentMaxLayer, otherNode.temp[0] + 1);
      currentMaxLayer = internalNode.temp[0];
    }
  }

  // Reset the maxRank to that which would be expected for a from-sink
  // scan
  this.maxRank = this.SOURCESCANSTARTRANK - this.maxRank;
};

/**
 * Function: fixRanks
 *
 * Fixes the layer assignments to the values stored in the nodes. Also needs
 * to create dummy nodes for edges that cross layers.
 */
mxGraphHierarchyModel.prototype.fixRanks = function () {
  var rankList = [];
  this.ranks = [];

  for (var i = 0; i < this.maxRank + 1; i++) {
    rankList[i] = [];
    this.ranks[i] = rankList[i];
  }

  // Perform a DFS to obtain an initial ordering for each rank.
  // Without doing this you would end up having to process
  // crossings for a standard tree.
  var rootsArray = null;

  if (this.roots != null) {
    var oldRootsArray = this.roots;
    rootsArray = [];

    for (var i = 0; i < oldRootsArray.length; i++) {
      var cell = oldRootsArray[i];
      var internalNode = this.vertexMapper.get(cell);
      rootsArray[i] = internalNode;
    }
  }

  this.visit(
    function (parent, node, edge, layer, seen) {
      if (seen == 0 && node.maxRank < 0 && node.minRank < 0) {
        rankList[node.temp[0]].push(node);
        node.maxRank = node.temp[0];
        node.minRank = node.temp[0];

        // Set temp[0] to the nodes position in the rank
        node.temp[0] = rankList[node.maxRank].length - 1;
      }

      if (parent != null && edge != null) {
        var parentToCellRankDifference = parent.maxRank - node.maxRank;

        if (parentToCellRankDifference > 1) {
          // There are ranks in between the parent and current cell
          edge.maxRank = parent.maxRank;
          edge.minRank = node.maxRank;
          edge.temp = [];
          edge.x = [];
          edge.y = [];

          for (var i = edge.minRank + 1; i < edge.maxRank; i++) {
            // The connecting edge must be added to the
            // appropriate ranks
            rankList[i].push(edge);
            edge.setGeneralPurposeVariable(i, rankList[i].length - 1);
          }
        }
      }
    },
    rootsArray,
    false,
    null
  );
};

/**
 * Function: visit
 *
 * A depth first search through the internal heirarchy model.
 *
 * Parameters:
 *
 * visitor - The visitor function pattern to be called for each node.
 * trackAncestors - Whether or not the search is to keep track all nodes
 * directly above this one in the search path.
 */
mxGraphHierarchyModel.prototype.visit = function (visitor, dfsRoots, trackAncestors, seenNodes) {
  // Run dfs through on all roots
  if (dfsRoots != null) {
    for (var i = 0; i < dfsRoots.length; i++) {
      var internalNode = dfsRoots[i];

      if (internalNode != null) {
        if (seenNodes == null) {
          seenNodes = new Object();
        }

        if (trackAncestors) {
          // Set up hash code for root
          internalNode.hashCode = [];
          internalNode.hashCode[0] = this.dfsCount;
          internalNode.hashCode[1] = i;
          this.extendedDfs(null, internalNode, null, visitor, seenNodes, internalNode.hashCode, i, 0);
        } else {
          this.dfs(null, internalNode, null, visitor, seenNodes, 0);
        }
      }
    }

    this.dfsCount++;
  }
};

/**
 * Function: dfs
 *
 * Performs a depth first search on the internal hierarchy model
 *
 * Parameters:
 *
 * parent - the parent internal node of the current internal node
 * root - the current internal node
 * connectingEdge - the internal edge connecting the internal node and the parent
 * internal node, if any
 * visitor - the visitor pattern to be called for each node
 * seen - a set of all nodes seen by this dfs a set of all of the
 * ancestor node of the current node
 * layer - the layer on the dfs tree ( not the same as the model ranks )
 */
mxGraphHierarchyModel.prototype.dfs = function (parent, root, connectingEdge, visitor, seen, layer) {
  if (root != null) {
    var rootId = root.id;

    if (seen[rootId] == null) {
      seen[rootId] = root;
      visitor(parent, root, connectingEdge, layer, 0);

      // Copy the connects as source list so that visitors
      // can change the original for edge direction inversions
      var outgoingEdges = root.connectsAsSource.slice();

      for (var i = 0; i < outgoingEdges.length; i++) {
        var internalEdge = outgoingEdges[i];
        var targetNode = internalEdge.target;

        // Root check is O(|roots|)
        this.dfs(root, targetNode, internalEdge, visitor, seen, layer + 1);
      }
    } else {
      // Use the int field to indicate this node has been seen
      visitor(parent, root, connectingEdge, layer, 1);
    }
  }
};

/**
 * Function: extendedDfs
 *
 * Performs a depth first search on the internal hierarchy model. This dfs
 * extends the default version by keeping track of cells ancestors, but it
 * should be only used when necessary because of it can be computationally
 * intensive for deep searches.
 *
 * Parameters:
 *
 * parent - the parent internal node of the current internal node
 * root - the current internal node
 * connectingEdge - the internal edge connecting the internal node and the parent
 * internal node, if any
 * visitor - the visitor pattern to be called for each node
 * seen - a set of all nodes seen by this dfs
 * ancestors - the parent hash code
 * childHash - the new hash code for this node
 * layer - the layer on the dfs tree ( not the same as the model ranks )
 */
mxGraphHierarchyModel.prototype.extendedDfs = function (
  parent,
  root,
  connectingEdge,
  visitor,
  seen,
  ancestors,
  childHash,
  layer
) {
  // Explanation of custom hash set. Previously, the ancestors variable
  // was passed through the dfs as a HashSet. The ancestors were copied
  // into a new HashSet and when the new child was processed it was also
  // added to the set. If the current node was in its ancestor list it
  // meant there is a cycle in the graph and this information is passed
  // to the visitor.visit() in the seen parameter. The HashSet clone was
  // very expensive on CPU so a custom hash was developed using primitive
  // types. temp[] couldn't be used so hashCode[] was added to each node.
  // Each new child adds another int to the array, copying the prefix
  // from its parent. Child of the same parent add different ints (the
  // limit is therefore 2^32 children per parent...). If a node has a
  // child with the hashCode already set then the child code is compared
  // to the same portion of the current nodes array. If they match there
  // is a loop.
  // Note that the basic mechanism would only allow for 1 use of this
  // functionality, so the root nodes have two ints. The second int is
  // incremented through each node root and the first is incremented
  // through each run of the dfs algorithm (therefore the dfs is not
  // thread safe). The hash code of each node is set if not already set,
  // or if the first int does not match that of the current run.
  if (root != null) {
    if (parent != null) {
      // Form this nodes hash code if necessary, that is, if the
      // hashCode variable has not been initialized or if the
      // start of the parent hash code does not equal the start of
      // this nodes hash code, indicating the code was set on a
      // previous run of this dfs.
      if (root.hashCode == null || root.hashCode[0] != parent.hashCode[0]) {
        var hashCodeLength = parent.hashCode.length + 1;
        root.hashCode = parent.hashCode.slice();
        root.hashCode[hashCodeLength - 1] = childHash;
      }
    }

    var rootId = root.id;

    if (seen[rootId] == null) {
      seen[rootId] = root;
      visitor(parent, root, connectingEdge, layer, 0);

      // Copy the connects as source list so that visitors
      // can change the original for edge direction inversions
      var outgoingEdges = root.connectsAsSource.slice();

      for (var i = 0; i < outgoingEdges.length; i++) {
        var internalEdge = outgoingEdges[i];
        var targetNode = internalEdge.target;

        // Root check is O(|roots|)
        this.extendedDfs(root, targetNode, internalEdge, visitor, seen, root.hashCode, i, layer + 1);
      }
    } else {
      // Use the int field to indicate this node has been seen
      visitor(parent, root, connectingEdge, layer, 1);
    }
  }
};

/**
 * layout/hierarchical/model/mxSwimlaneModel.js
 */
/**
 * Copyright (c) 2006-2018, JGraph Ltd
 * Copyright (c) 2006-2018, Gaudenz Alder
 */
/**
 * Class: mxSwimlaneModel
 *
 * Internal model of a hierarchical graph. This model stores nodes and edges
 * equivalent to the real graph nodes and edges, but also stores the rank of the
 * cells, the order within the ranks and the new candidate locations of cells.
 * The internal model also reverses edge direction were appropriate , ignores
 * self-loop and groups parallels together under one edge object.
 *
 * Constructor: mxSwimlaneModel
 *
 * Creates an internal ordered graph model using the vertices passed in. If
 * there are any, leftward edge need to be inverted in the internal model
 *
 * Arguments:
 *
 * graph - the facade describing the graph to be operated on
 * vertices - the vertices for this hierarchy
 * ordered - whether or not the vertices are already ordered
 * deterministic - whether or not this layout should be deterministic on each
 * tightenToSource - whether or not to tighten vertices towards the sources
 * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
 * usage
 */
function mxSwimlaneModel(layout, vertices, roots, parent, tightenToSource) {
  var graph = layout.getGraph();
  this.tightenToSource = tightenToSource;
  this.roots = roots;
  this.parent = parent;

  // map of cells to internal cell needed for second run through
  // to setup the sink of edges correctly
  this.vertexMapper = new mxDictionary();
  this.edgeMapper = new mxDictionary();
  this.maxRank = 0;
  var internalVertices = [];

  if (vertices == null) {
    vertices = this.graph.getChildVertices(parent);
  }

  this.maxRank = this.SOURCESCANSTARTRANK;
  // map of cells to internal cell needed for second run through
  // to setup the sink of edges correctly. Guess size by number
  // of edges is roughly same as number of vertices.
  this.createInternalCells(layout, vertices, internalVertices);

  // Go through edges set their sink values. Also check the
  // ordering if and invert edges if necessary
  for (var i = 0; i < vertices.length; i++) {
    var edges = internalVertices[i].connectsAsSource;

    for (var j = 0; j < edges.length; j++) {
      var internalEdge = edges[j];
      var realEdges = internalEdge.edges;

      // Only need to process the first real edge, since
      // all the edges connect to the same other vertex
      if (realEdges != null && realEdges.length > 0) {
        var realEdge = realEdges[0];
        var targetCell = layout.getVisibleTerminal(realEdge, false);
        var internalTargetCell = this.vertexMapper.get(targetCell);

        if (internalVertices[i] == internalTargetCell) {
          // If there are parallel edges going between two vertices and not all are in the same direction
          // you can have navigated across one direction when doing the cycle reversal that isn't the same
          // direction as the first real edge in the array above. When that happens the if above catches
          // that and we correct the target cell before continuing.
          // This branch only detects this single case
          targetCell = layout.getVisibleTerminal(realEdge, true);
          internalTargetCell = this.vertexMapper.get(targetCell);
        }

        if (internalTargetCell != null && internalVertices[i] != internalTargetCell) {
          internalEdge.target = internalTargetCell;

          if (internalTargetCell.connectsAsTarget.length == 0) {
            internalTargetCell.connectsAsTarget = [];
          }

          if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0) {
            internalTargetCell.connectsAsTarget.push(internalEdge);
          }
        }
      }
    }

    // Use the temp variable in the internal nodes to mark this
    // internal vertex as having been visited.
    internalVertices[i].temp[0] = 1;
  }
}

/**
 * Variable: maxRank
 *
 * Stores the largest rank number allocated
 */
mxSwimlaneModel.prototype.maxRank = null;

/**
 * Variable: vertexMapper
 *
 * Map from graph vertices to internal model nodes.
 */
mxSwimlaneModel.prototype.vertexMapper = null;

/**
 * Variable: edgeMapper
 *
 * Map from graph edges to internal model edges
 */
mxSwimlaneModel.prototype.edgeMapper = null;

/**
 * Variable: ranks
 *
 * Mapping from rank number to actual rank
 */
mxSwimlaneModel.prototype.ranks = null;

/**
 * Variable: roots
 *
 * Store of roots of this hierarchy model, these are real graph cells, not
 * internal cells
 */
mxSwimlaneModel.prototype.roots = null;

/**
 * Variable: parent
 *
 * The parent cell whose children are being laid out
 */
mxSwimlaneModel.prototype.parent = null;

/**
 * Variable: dfsCount
 *
 * Count of the number of times the ancestor dfs has been used.
 */
mxSwimlaneModel.prototype.dfsCount = 0;

/**
 * Variable: SOURCESCANSTARTRANK
 *
 * High value to start source layering scan rank value from.
 */
mxSwimlaneModel.prototype.SOURCESCANSTARTRANK = 100000000;

/**
 * Variable: tightenToSource
 *
 * Whether or not to tighten the assigned ranks of vertices up towards
 * the source cells.
 */
mxSwimlaneModel.prototype.tightenToSource = false;

/**
 * Variable: ranksPerGroup
 *
 * An array of the number of ranks within each swimlane
 */
mxSwimlaneModel.prototype.ranksPerGroup = null;

/**
 * Function: createInternalCells
 *
 * Creates all edges in the internal model
 *
 * Parameters:
 *
 * layout - Reference to the <mxHierarchicalLayout> algorithm.
 * vertices - Array of <mxCells> that represent the vertices whom are to
 * have an internal representation created.
 * internalVertices - The array of <mxGraphHierarchyNodes> to have their
 * information filled in using the real vertices.
 */
mxSwimlaneModel.prototype.createInternalCells = function (layout, vertices, internalVertices) {
  var graph = layout.getGraph();
  var swimlanes = layout.swimlanes;

  // Create internal edges
  for (var i = 0; i < vertices.length; i++) {
    internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
    this.vertexMapper.put(vertices[i], internalVertices[i]);
    internalVertices[i].swimlaneIndex = -1;

    for (var ii = 0; ii < swimlanes.length; ii++) {
      if (graph.model.getParent(vertices[i]) == swimlanes[ii]) {
        internalVertices[i].swimlaneIndex = ii;
        break;
      }
    }

    // If the layout is deterministic, order the cells
    //List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
    var conns = layout.getEdges(vertices[i]);
    internalVertices[i].connectsAsSource = [];

    // Create internal edges, but don't do any rank assignment yet
    // First use the information from the greedy cycle remover to
    // invert the leftward edges internally
    for (var j = 0; j < conns.length; j++) {
      var cell = layout.getVisibleTerminal(conns[j], false);

      // Looking for outgoing edges only
      if (cell != vertices[i] && layout.graph.model.isVertex(cell) && !layout.isVertexIgnored(cell)) {
        // We process all edge between this source and its targets
        // If there are edges going both ways, we need to collect
        // them all into one internal edges to avoid looping problems
        // later. We assume this direction (source -> target) is the
        // natural direction if at least half the edges are going in
        // that direction.

        // The check below for edges[0] being in the vertex mapper is
        // in case we've processed this the other way around
        // (target -> source) and the number of edges in each direction
        // are the same. All the graph edges will have been assigned to
        // an internal edge going the other way, so we don't want to
        // process them again
        var undirectedEdges = layout.getEdgesBetween(vertices[i], cell, false);
        var directedEdges = layout.getEdgesBetween(vertices[i], cell, true);

        if (
          undirectedEdges != null &&
          undirectedEdges.length > 0 &&
          this.edgeMapper.get(undirectedEdges[0]) == null &&
          directedEdges.length * 2 >= undirectedEdges.length
        ) {
          var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);

          for (var k = 0; k < undirectedEdges.length; k++) {
            var edge = undirectedEdges[k];
            this.edgeMapper.put(edge, internalEdge);

            // Resets all point on the edge and disables the edge style
            // without deleting it from the cell style
            graph.resetEdge(edge);

            if (layout.disableEdgeStyle) {
              layout.setEdgeStyleEnabled(edge, false);
              layout.setOrthogonalEdge(edge, true);
            }
          }

          internalEdge.source = internalVertices[i];

          if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0) {
            internalVertices[i].connectsAsSource.push(internalEdge);
          }
        }
      }
    }

    // Ensure temp variable is cleared from any previous use
    internalVertices[i].temp[0] = 0;
  }
};

/**
 * Function: initialRank
 *
 * Basic determination of minimum layer ranking by working from from sources
 * or sinks and working through each node in the relevant edge direction.
 * Starting at the sinks is basically a longest path layering algorithm.
 */
mxSwimlaneModel.prototype.initialRank = function () {
  this.ranksPerGroup = [];

  var startNodes = [];
  var seen = new Object();

  if (this.roots != null) {
    for (var i = 0; i < this.roots.length; i++) {
      var internalNode = this.vertexMapper.get(this.roots[i]);
      this.maxChainDfs(null, internalNode, null, seen, 0);

      if (internalNode != null) {
        startNodes.push(internalNode);
      }
    }
  }

  // Calculate the lower and upper rank bounds of each swimlane
  var lowerRank = [];
  var upperRank = [];

  for (var i = this.ranksPerGroup.length - 1; i >= 0; i--) {
    if (i == this.ranksPerGroup.length - 1) {
      lowerRank[i] = 0;
    } else {
      lowerRank[i] = upperRank[i + 1] + 1;
    }

    upperRank[i] = lowerRank[i] + this.ranksPerGroup[i];
  }

  this.maxRank = upperRank[0];

  var internalNodes = this.vertexMapper.getValues();

  for (var i = 0; i < internalNodes.length; i++) {
    // Mark the node as not having had a layer assigned
    internalNodes[i].temp[0] = -1;
  }

  var startNodesCopy = startNodes.slice();

  while (startNodes.length > 0) {
    var internalNode = startNodes[0];
    var layerDeterminingEdges;
    var edgesToBeMarked;

    layerDeterminingEdges = internalNode.connectsAsTarget;
    edgesToBeMarked = internalNode.connectsAsSource;

    // flag to keep track of whether or not all layer determining
    // edges have been scanned
    var allEdgesScanned = true;

    // Work out the layer of this node from the layer determining
    // edges. The minimum layer number of any node connected by one of
    // the layer determining edges variable
    var minimumLayer = upperRank[0];

    for (var i = 0; i < layerDeterminingEdges.length; i++) {
      var internalEdge = layerDeterminingEdges[i];

      if (internalEdge.temp[0] == 5270620) {
        // This edge has been scanned, get the layer of the
        // node on the other end
        var otherNode = internalEdge.source;
        minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
      } else {
        allEdgesScanned = false;

        break;
      }
    }

    // If all edge have been scanned, assign the layer, mark all
    // edges in the other direction and remove from the nodes list
    if (allEdgesScanned) {
      if (minimumLayer > upperRank[internalNode.swimlaneIndex]) {
        minimumLayer = upperRank[internalNode.swimlaneIndex];
      }

      internalNode.temp[0] = minimumLayer;

      if (edgesToBeMarked != null) {
        for (var i = 0; i < edgesToBeMarked.length; i++) {
          var internalEdge = edgesToBeMarked[i];

          // Assign unique stamp ( y/m/d/h )
          internalEdge.temp[0] = 5270620;

          // Add node on other end of edge to LinkedList of
          // nodes to be analysed
          var otherNode = internalEdge.target;

          // Only add node if it hasn't been assigned a layer
          if (otherNode.temp[0] == -1) {
            startNodes.push(otherNode);

            // Mark this other node as neither being
            // unassigned nor assigned so it isn't
            // added to this list again, but it's
            // layer isn't used in any calculation.
            otherNode.temp[0] = -2;
          }
        }
      }

      startNodes.shift();
    } else {
      // Not all the edges have been scanned, get to the back of
      // the class and put the dunces cap on
      var removedCell = startNodes.shift();
      startNodes.push(internalNode);

      if (removedCell == internalNode && startNodes.length == 1) {
        // This is an error condition, we can't get out of
        // this loop. It could happen for more than one node
        // but that's a lot harder to detect. Log the error
        // TODO make log comment
        break;
      }
    }
  }

  // Normalize the ranks down from their large starting value to place
  // at least 1 sink on layer 0
  //	for (var key in this.vertexMapper)
  //	{
  //		var internalNode = this.vertexMapper[key];
  //		// Mark the node as not having had a layer assigned
  //		internalNode.temp[0] -= this.maxRank;
  //	}

  // Tighten the rank 0 nodes as far as possible
  //	for ( var i = 0; i < startNodesCopy.length; i++)
  //	{
  //		var internalNode = startNodesCopy[i];
  //		var currentMaxLayer = 0;
  //		var layerDeterminingEdges = internalNode.connectsAsSource;
  //
  //		for ( var j = 0; j < layerDeterminingEdges.length; j++)
  //		{
  //			var internalEdge = layerDeterminingEdges[j];
  //			var otherNode = internalEdge.target;
  //			internalNode.temp[0] = Math.max(currentMaxLayer,
  //					otherNode.temp[0] + 1);
  //			currentMaxLayer = internalNode.temp[0];
  //		}
  //	}
};

/**
 * Function: maxChainDfs
 *
 * Performs a depth first search on the internal hierarchy model. This dfs
 * extends the default version by keeping track of chains within groups.
 * Any cycles should be removed prior to running, but previously seen cells
 * are ignored.
 *
 * Parameters:
 *
 * parent - the parent internal node of the current internal node
 * root - the current internal node
 * connectingEdge - the internal edge connecting the internal node and the parent
 * internal node, if any
 * seen - a set of all nodes seen by this dfs
 * chainCount - the number of edges in the chain of vertices going through
 * the current swimlane
 */
mxSwimlaneModel.prototype.maxChainDfs = function (parent, root, connectingEdge, seen, chainCount) {
  if (root != null) {
    var rootId = mxCellPath.create(root.cell);

    if (seen[rootId] == null) {
      seen[rootId] = root;
      var slIndex = root.swimlaneIndex;

      if (this.ranksPerGroup[slIndex] == null || this.ranksPerGroup[slIndex] < chainCount) {
        this.ranksPerGroup[slIndex] = chainCount;
      }

      // Copy the connects as source list so that visitors
      // can change the original for edge direction inversions
      var outgoingEdges = root.connectsAsSource.slice();

      for (var i = 0; i < outgoingEdges.length; i++) {
        var internalEdge = outgoingEdges[i];
        var targetNode = internalEdge.target;

        // Only navigate in source->target direction within the same
        // swimlane, or from a lower index swimlane to a higher one
        if (root.swimlaneIndex < targetNode.swimlaneIndex) {
          this.maxChainDfs(root, targetNode, internalEdge, mxUtils.clone(seen, null, true), 0);
        } else if (root.swimlaneIndex == targetNode.swimlaneIndex) {
          this.maxChainDfs(root, targetNode, internalEdge, mxUtils.clone(seen, null, true), chainCount + 1);
        }
      }
    }
  }
};

/**
 * Function: fixRanks
 *
 * Fixes the layer assignments to the values stored in the nodes. Also needs
 * to create dummy nodes for edges that cross layers.
 */
mxSwimlaneModel.prototype.fixRanks = function () {
  var rankList = [];
  this.ranks = [];

  for (var i = 0; i < this.maxRank + 1; i++) {
    rankList[i] = [];
    this.ranks[i] = rankList[i];
  }

  // Perform a DFS to obtain an initial ordering for each rank.
  // Without doing this you would end up having to process
  // crossings for a standard tree.
  var rootsArray = null;

  if (this.roots != null) {
    var oldRootsArray = this.roots;
    rootsArray = [];

    for (var i = 0; i < oldRootsArray.length; i++) {
      var cell = oldRootsArray[i];
      var internalNode = this.vertexMapper.get(cell);
      rootsArray[i] = internalNode;
    }
  }

  this.visit(
    function (parent, node, edge, layer, seen) {
      if (seen == 0 && node.maxRank < 0 && node.minRank < 0) {
        rankList[node.temp[0]].push(node);
        node.maxRank = node.temp[0];
        node.minRank = node.temp[0];

        // Set temp[0] to the nodes position in the rank
        node.temp[0] = rankList[node.maxRank].length - 1;
      }

      if (parent != null && edge != null) {
        var parentToCellRankDifference = parent.maxRank - node.maxRank;

        if (parentToCellRankDifference > 1) {
          // There are ranks in between the parent and current cell
          edge.maxRank = parent.maxRank;
          edge.minRank = node.maxRank;
          edge.temp = [];
          edge.x = [];
          edge.y = [];

          for (var i = edge.minRank + 1; i < edge.maxRank; i++) {
            // The connecting edge must be added to the
            // appropriate ranks
            rankList[i].push(edge);
            edge.setGeneralPurposeVariable(i, rankList[i].length - 1);
          }
        }
      }
    },
    rootsArray,
    false,
    null
  );
};

/**
 * Function: visit
 *
 * A depth first search through the internal heirarchy model.
 *
 * Parameters:
 *
 * visitor - The visitor function pattern to be called for each node.
 * trackAncestors - Whether or not the search is to keep track all nodes
 * directly above this one in the search path.
 */
mxSwimlaneModel.prototype.visit = function (visitor, dfsRoots, trackAncestors, seenNodes) {
  // Run dfs through on all roots
  if (dfsRoots != null) {
    for (var i = 0; i < dfsRoots.length; i++) {
      var internalNode = dfsRoots[i];

      if (internalNode != null) {
        if (seenNodes == null) {
          seenNodes = new Object();
        }

        if (trackAncestors) {
          // Set up hash code for root
          internalNode.hashCode = [];
          internalNode.hashCode[0] = this.dfsCount;
          internalNode.hashCode[1] = i;
          this.extendedDfs(null, internalNode, null, visitor, seenNodes, internalNode.hashCode, i, 0);
        } else {
          this.dfs(null, internalNode, null, visitor, seenNodes, 0);
        }
      }
    }

    this.dfsCount++;
  }
};

/**
 * Function: dfs
 *
 * Performs a depth first search on the internal hierarchy model
 *
 * Parameters:
 *
 * parent - the parent internal node of the current internal node
 * root - the current internal node
 * connectingEdge - the internal edge connecting the internal node and the parent
 * internal node, if any
 * visitor - the visitor pattern to be called for each node
 * seen - a set of all nodes seen by this dfs a set of all of the
 * ancestor node of the current node
 * layer - the layer on the dfs tree ( not the same as the model ranks )
 */
mxSwimlaneModel.prototype.dfs = function (parent, root, connectingEdge, visitor, seen, layer) {
  if (root != null) {
    var rootId = root.id;

    if (seen[rootId] == null) {
      seen[rootId] = root;
      visitor(parent, root, connectingEdge, layer, 0);

      // Copy the connects as source list so that visitors
      // can change the original for edge direction inversions
      var outgoingEdges = root.connectsAsSource.slice();

      for (var i = 0; i < outgoingEdges.length; i++) {
        var internalEdge = outgoingEdges[i];
        var targetNode = internalEdge.target;

        // Root check is O(|roots|)
        this.dfs(root, targetNode, internalEdge, visitor, seen, layer + 1);
      }
    } else {
      // Use the int field to indicate this node has been seen
      visitor(parent, root, connectingEdge, layer, 1);
    }
  }
};

/**
 * Function: extendedDfs
 *
 * Performs a depth first search on the internal hierarchy model. This dfs
 * extends the default version by keeping track of cells ancestors, but it
 * should be only used when necessary because of it can be computationally
 * intensive for deep searches.
 *
 * Parameters:
 *
 * parent - the parent internal node of the current internal node
 * root - the current internal node
 * connectingEdge - the internal edge connecting the internal node and the parent
 * internal node, if any
 * visitor - the visitor pattern to be called for each node
 * seen - a set of all nodes seen by this dfs
 * ancestors - the parent hash code
 * childHash - the new hash code for this node
 * layer - the layer on the dfs tree ( not the same as the model ranks )
 */
mxSwimlaneModel.prototype.extendedDfs = function (
  parent,
  root,
  connectingEdge,
  visitor,
  seen,
  ancestors,
  childHash,
  layer
) {
  // Explanation of custom hash set. Previously, the ancestors variable
  // was passed through the dfs as a HashSet. The ancestors were copied
  // into a new HashSet and when the new child was processed it was also
  // added to the set. If the current node was in its ancestor list it
  // meant there is a cycle in the graph and this information is passed
  // to the visitor.visit() in the seen parameter. The HashSet clone was
  // very expensive on CPU so a custom hash was developed using primitive
  // types. temp[] couldn't be used so hashCode[] was added to each node.
  // Each new child adds another int to the array, copying the prefix
  // from its parent. Child of the same parent add different ints (the
  // limit is therefore 2^32 children per parent...). If a node has a
  // child with the hashCode already set then the child code is compared
  // to the same portion of the current nodes array. If they match there
  // is a loop.
  // Note that the basic mechanism would only allow for 1 use of this
  // functionality, so the root nodes have two ints. The second int is
  // incremented through each node root and the first is incremented
  // through each run of the dfs algorithm (therefore the dfs is not
  // thread safe). The hash code of each node is set if not already set,
  // or if the first int does not match that of the current run.
  if (root != null) {
    if (parent != null) {
      // Form this nodes hash code if necessary, that is, if the
      // hashCode variable has not been initialized or if the
      // start of the parent hash code does not equal the start of
      // this nodes hash code, indicating the code was set on a
      // previous run of this dfs.
      if (root.hashCode == null || root.hashCode[0] != parent.hashCode[0]) {
        var hashCodeLength = parent.hashCode.length + 1;
        root.hashCode = parent.hashCode.slice();
        root.hashCode[hashCodeLength - 1] = childHash;
      }
    }

    var rootId = root.id;

    if (seen[rootId] == null) {
      seen[rootId] = root;
      visitor(parent, root, connectingEdge, layer, 0);

      // Copy the connects as source list so that visitors
      // can change the original for edge direction inversions
      var outgoingEdges = root.connectsAsSource.slice();
      var incomingEdges = root.connectsAsTarget.slice();

      for (var i = 0; i < outgoingEdges.length; i++) {
        var internalEdge = outgoingEdges[i];
        var targetNode = internalEdge.target;

        // Only navigate in source->target direction within the same
        // swimlane, or from a lower index swimlane to a higher one
        if (root.swimlaneIndex <= targetNode.swimlaneIndex) {
          this.extendedDfs(root, targetNode, internalEdge, visitor, seen, root.hashCode, i, layer + 1);
        }
      }

      for (var i = 0; i < incomingEdges.length; i++) {
        var internalEdge = incomingEdges[i];
        var targetNode = internalEdge.source;

        // Only navigate in target->source direction from a lower index
        // swimlane to a higher one
        if (root.swimlaneIndex < targetNode.swimlaneIndex) {
          this.extendedDfs(root, targetNode, internalEdge, visitor, seen, root.hashCode, i, layer + 1);
        }
      }
    } else {
      // Use the int field to indicate this node has been seen
      visitor(parent, root, connectingEdge, layer, 1);
    }
  }
};

/**
 * layout/hierarchical/stage/mxHierarchicalLayoutStage.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxHierarchicalLayoutStage
 *
 * The specific layout interface for hierarchical layouts. It adds a
 * <code>run</code> method with a parameter for the hierarchical layout model
 * that is shared between the layout stages.
 *
 * Constructor: mxHierarchicalLayoutStage
 *
 * Constructs a new hierarchical layout stage.
 */
function mxHierarchicalLayoutStage() {}

/**
 * Function: execute
 *
 * Takes the graph detail and configuration information within the facade
 * and creates the resulting laid out graph within that facade for further
 * use.
 */
mxHierarchicalLayoutStage.prototype.execute = function (parent) {};

/**
 * layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxMedianHybridCrossingReduction
 *
 * Sets the horizontal locations of node and edge dummy nodes on each layer.
 * Uses median down and up weighings as well heuristic to straighten edges as
 * far as possible.
 *
 * Constructor: mxMedianHybridCrossingReduction
 *
 * Creates a coordinate assignment.
 *
 * Arguments:
 *
 * intraCellSpacing - the minimum buffer between cells on the same rank
 * interRankCellSpacing - the minimum distance between cells on adjacent ranks
 * orientation - the position of the root node(s) relative to the graph
 * initialX - the leftmost coordinate node placement starts at
 */
function mxMedianHybridCrossingReduction(layout) {
  this.layout = layout;
}

/**
 * Extends mxMedianHybridCrossingReduction.
 */
mxMedianHybridCrossingReduction.prototype = new mxHierarchicalLayoutStage();
mxMedianHybridCrossingReduction.prototype.constructor = mxMedianHybridCrossingReduction;

/**
 * Variable: layout
 *
 * Reference to the enclosing <mxHierarchicalLayout>.
 */
mxMedianHybridCrossingReduction.prototype.layout = null;

/**
 * Variable: maxIterations
 *
 * The maximum number of iterations to perform whilst reducing edge
 * crossings. Default is 24.
 */
mxMedianHybridCrossingReduction.prototype.maxIterations = 24;

/**
 * Variable: nestedBestRanks
 *
 * Stores each rank as a collection of cells in the best order found for
 * each layer so far
 */
mxMedianHybridCrossingReduction.prototype.nestedBestRanks = null;

/**
 * Variable: currentBestCrossings
 *
 * The total number of crossings found in the best configuration so far
 */
mxMedianHybridCrossingReduction.prototype.currentBestCrossings = 0;

/**
 * Variable: iterationsWithoutImprovement
 *
 * The total number of crossings found in the best configuration so far
 */
mxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement = 0;

/**
 * Variable: maxNoImprovementIterations
 *
 * The total number of crossings found in the best configuration so far
 */
mxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations = 2;

/**
 * Function: execute
 *
 * Performs a vertex ordering within ranks as described by Gansner et al
 * 1993
 */
mxMedianHybridCrossingReduction.prototype.execute = function (parent) {
  var model = this.layout.getModel();

  // Stores initial ordering as being the best one found so far
  this.nestedBestRanks = [];

  for (var i = 0; i < model.ranks.length; i++) {
    this.nestedBestRanks[i] = model.ranks[i].slice();
  }

  var iterationsWithoutImprovement = 0;
  var currentBestCrossings = this.calculateCrossings(model);

  for (var i = 0; i < this.maxIterations && iterationsWithoutImprovement < this.maxNoImprovementIterations; i++) {
    this.weightedMedian(i, model);
    this.transpose(i, model);
    var candidateCrossings = this.calculateCrossings(model);

    if (candidateCrossings < currentBestCrossings) {
      currentBestCrossings = candidateCrossings;
      iterationsWithoutImprovement = 0;

      // Store the current rankings as the best ones
      for (var j = 0; j < this.nestedBestRanks.length; j++) {
        var rank = model.ranks[j];

        for (var k = 0; k < rank.length; k++) {
          var cell = rank[k];
          this.nestedBestRanks[j][cell.getGeneralPurposeVariable(j)] = cell;
        }
      }
    } else {
      // Increase count of iterations where we haven't improved the
      // layout
      iterationsWithoutImprovement++;

      // Restore the best values to the cells
      for (var j = 0; j < this.nestedBestRanks.length; j++) {
        var rank = model.ranks[j];

        for (var k = 0; k < rank.length; k++) {
          var cell = rank[k];
          cell.setGeneralPurposeVariable(j, k);
        }
      }
    }

    if (currentBestCrossings == 0) {
      // Do nothing further
      break;
    }
  }

  // Store the best rankings but in the model
  var ranks = [];
  var rankList = [];

  for (var i = 0; i < model.maxRank + 1; i++) {
    rankList[i] = [];
    ranks[i] = rankList[i];
  }

  for (var i = 0; i < this.nestedBestRanks.length; i++) {
    for (var j = 0; j < this.nestedBestRanks[i].length; j++) {
      rankList[i].push(this.nestedBestRanks[i][j]);
    }
  }

  model.ranks = ranks;
};

/**
 * Function: calculateCrossings
 *
 * Calculates the total number of edge crossing in the current graph.
 * Returns the current number of edge crossings in the hierarchy graph
 * model in the current candidate layout
 *
 * Parameters:
 *
 * model - the internal model describing the hierarchy
 */
mxMedianHybridCrossingReduction.prototype.calculateCrossings = function (model) {
  var numRanks = model.ranks.length;
  var totalCrossings = 0;

  for (var i = 1; i < numRanks; i++) {
    totalCrossings += this.calculateRankCrossing(i, model);
  }

  return totalCrossings;
};

/**
 * Function: calculateRankCrossing
 *
 * Calculates the number of edges crossings between the specified rank and
 * the rank below it. Returns the number of edges crossings with the rank
 * beneath
 *
 * Parameters:
 *
 * i -  the topmost rank of the pair ( higher rank value )
 * model - the internal model describing the hierarchy
 */
mxMedianHybridCrossingReduction.prototype.calculateRankCrossing = function (i, model) {
  var totalCrossings = 0;
  var rank = model.ranks[i];
  var previousRank = model.ranks[i - 1];

  var tmpIndices = [];

  // Iterate over the top rank and fill in the connection information
  for (var j = 0; j < rank.length; j++) {
    var node = rank[j];
    var rankPosition = node.getGeneralPurposeVariable(i);
    var connectedCells = node.getPreviousLayerConnectedCells(i);
    var nodeIndices = [];

    for (var k = 0; k < connectedCells.length; k++) {
      var connectedNode = connectedCells[k];
      var otherCellRankPosition = connectedNode.getGeneralPurposeVariable(i - 1);
      nodeIndices.push(otherCellRankPosition);
    }

    nodeIndices.sort(function (x, y) {
      return x - y;
    });
    tmpIndices[rankPosition] = nodeIndices;
  }

  var indices = [];

  for (var j = 0; j < tmpIndices.length; j++) {
    indices = indices.concat(tmpIndices[j]);
  }

  var firstIndex = 1;

  while (firstIndex < previousRank.length) {
    firstIndex <<= 1;
  }

  var treeSize = 2 * firstIndex - 1;
  firstIndex -= 1;

  var tree = [];

  for (var j = 0; j < treeSize; ++j) {
    tree[j] = 0;
  }

  for (var j = 0; j < indices.length; j++) {
    var index = indices[j];
    var treeIndex = index + firstIndex;
    ++tree[treeIndex];

    while (treeIndex > 0) {
      if (treeIndex % 2) {
        totalCrossings += tree[treeIndex + 1];
      }

      treeIndex = (treeIndex - 1) >> 1;
      ++tree[treeIndex];
    }
  }

  return totalCrossings;
};

/**
 * Function: transpose
 *
 * Takes each possible adjacent cell pair on each rank and checks if
 * swapping them around reduces the number of crossing
 *
 * Parameters:
 *
 * mainLoopIteration - the iteration number of the main loop
 * model - the internal model describing the hierarchy
 */
mxMedianHybridCrossingReduction.prototype.transpose = function (mainLoopIteration, model) {
  var improved = true;

  // Track the number of iterations in case of looping
  var count = 0;
  var maxCount = 10;
  while (improved && count++ < maxCount) {
    // On certain iterations allow allow swapping of cell pairs with
    // equal edge crossings switched or not switched. This help to
    // nudge a stuck layout into a lower crossing total.
    var nudge = mainLoopIteration % 2 == 1 && count % 2 == 1;
    improved = false;

    for (var i = 0; i < model.ranks.length; i++) {
      var rank = model.ranks[i];
      var orderedCells = [];

      for (var j = 0; j < rank.length; j++) {
        var cell = rank[j];
        var tempRank = cell.getGeneralPurposeVariable(i);

        // FIXME: Workaround to avoid negative tempRanks
        if (tempRank < 0) {
          tempRank = j;
        }
        orderedCells[tempRank] = cell;
      }

      var leftCellAboveConnections = null;
      var leftCellBelowConnections = null;
      var rightCellAboveConnections = null;
      var rightCellBelowConnections = null;

      var leftAbovePositions = null;
      var leftBelowPositions = null;
      var rightAbovePositions = null;
      var rightBelowPositions = null;

      var leftCell = null;
      var rightCell = null;

      for (var j = 0; j < rank.length - 1; j++) {
        // For each intra-rank adjacent pair of cells
        // see if swapping them around would reduce the
        // number of edges crossing they cause in total
        // On every cell pair except the first on each rank, we
        // can save processing using the previous values for the
        // right cell on the new left cell
        if (j == 0) {
          leftCell = orderedCells[j];
          leftCellAboveConnections = leftCell.getNextLayerConnectedCells(i);
          leftCellBelowConnections = leftCell.getPreviousLayerConnectedCells(i);
          leftAbovePositions = [];
          leftBelowPositions = [];

          for (var k = 0; k < leftCellAboveConnections.length; k++) {
            leftAbovePositions[k] = leftCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
          }

          for (var k = 0; k < leftCellBelowConnections.length; k++) {
            leftBelowPositions[k] = leftCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
          }
        } else {
          leftCellAboveConnections = rightCellAboveConnections;
          leftCellBelowConnections = rightCellBelowConnections;
          leftAbovePositions = rightAbovePositions;
          leftBelowPositions = rightBelowPositions;
          leftCell = rightCell;
        }

        rightCell = orderedCells[j + 1];
        rightCellAboveConnections = rightCell.getNextLayerConnectedCells(i);
        rightCellBelowConnections = rightCell.getPreviousLayerConnectedCells(i);

        rightAbovePositions = [];
        rightBelowPositions = [];

        for (var k = 0; k < rightCellAboveConnections.length; k++) {
          rightAbovePositions[k] = rightCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
        }

        for (var k = 0; k < rightCellBelowConnections.length; k++) {
          rightBelowPositions[k] = rightCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
        }

        var totalCurrentCrossings = 0;
        var totalSwitchedCrossings = 0;

        for (var k = 0; k < leftAbovePositions.length; k++) {
          for (var ik = 0; ik < rightAbovePositions.length; ik++) {
            if (leftAbovePositions[k] > rightAbovePositions[ik]) {
              totalCurrentCrossings++;
            }

            if (leftAbovePositions[k] < rightAbovePositions[ik]) {
              totalSwitchedCrossings++;
            }
          }
        }

        for (var k = 0; k < leftBelowPositions.length; k++) {
          for (var ik = 0; ik < rightBelowPositions.length; ik++) {
            if (leftBelowPositions[k] > rightBelowPositions[ik]) {
              totalCurrentCrossings++;
            }

            if (leftBelowPositions[k] < rightBelowPositions[ik]) {
              totalSwitchedCrossings++;
            }
          }
        }

        if (
          totalSwitchedCrossings < totalCurrentCrossings ||
          (totalSwitchedCrossings == totalCurrentCrossings && nudge)
        ) {
          var temp = leftCell.getGeneralPurposeVariable(i);
          leftCell.setGeneralPurposeVariable(i, rightCell.getGeneralPurposeVariable(i));
          rightCell.setGeneralPurposeVariable(i, temp);

          // With this pair exchanged we have to switch all of
          // values for the left cell to the right cell so the
          // next iteration for this rank uses it as the left
          // cell again
          rightCellAboveConnections = leftCellAboveConnections;
          rightCellBelowConnections = leftCellBelowConnections;
          rightAbovePositions = leftAbovePositions;
          rightBelowPositions = leftBelowPositions;
          rightCell = leftCell;

          if (!nudge) {
            // Don't count nudges as improvement or we'll end
            // up stuck in two combinations and not finishing
            // as early as we should
            improved = true;
          }
        }
      }
    }
  }
};

/**
 * Function: weightedMedian
 *
 * Sweeps up or down the layout attempting to minimise the median placement
 * of connected cells on adjacent ranks
 *
 * Parameters:
 *
 * iteration - the iteration number of the main loop
 * model - the internal model describing the hierarchy
 */
mxMedianHybridCrossingReduction.prototype.weightedMedian = function (iteration, model) {
  // Reverse sweep direction each time through this method
  var downwardSweep = iteration % 2 == 0;
  if (downwardSweep) {
    for (var j = model.maxRank - 1; j >= 0; j--) {
      this.medianRank(j, downwardSweep);
    }
  } else {
    for (var j = 1; j < model.maxRank; j++) {
      this.medianRank(j, downwardSweep);
    }
  }
};

/**
 * Function: medianRank
 *
 * Attempts to minimise the median placement of connected cells on this rank
 * and one of the adjacent ranks
 *
 * Parameters:
 *
 * rankValue - the layer number of this rank
 * downwardSweep - whether or not this is a downward sweep through the graph
 */
mxMedianHybridCrossingReduction.prototype.medianRank = function (rankValue, downwardSweep) {
  var numCellsForRank = this.nestedBestRanks[rankValue].length;
  var medianValues = [];
  var reservedPositions = [];

  for (var i = 0; i < numCellsForRank; i++) {
    var cell = this.nestedBestRanks[rankValue][i];
    var sorterEntry = new MedianCellSorter();
    sorterEntry.cell = cell;

    // Flip whether or not equal medians are flipped on up and down
    // sweeps
    // TODO re-implement some kind of nudge
    // medianValues[i].nudge = !downwardSweep;
    var nextLevelConnectedCells;

    if (downwardSweep) {
      nextLevelConnectedCells = cell.getNextLayerConnectedCells(rankValue);
    } else {
      nextLevelConnectedCells = cell.getPreviousLayerConnectedCells(rankValue);
    }

    var nextRankValue;

    if (downwardSweep) {
      nextRankValue = rankValue + 1;
    } else {
      nextRankValue = rankValue - 1;
    }

    if (nextLevelConnectedCells != null && nextLevelConnectedCells.length != 0) {
      sorterEntry.medianValue = this.medianValue(nextLevelConnectedCells, nextRankValue);
      medianValues.push(sorterEntry);
    } else {
      // Nodes with no adjacent vertices are flagged in the reserved array
      // to indicate they should be left in their current position.
      reservedPositions[cell.getGeneralPurposeVariable(rankValue)] = true;
    }
  }

  medianValues.sort(MedianCellSorter.prototype.compare);

  // Set the new position of each node within the rank using
  // its temp variable
  for (var i = 0; i < numCellsForRank; i++) {
    if (reservedPositions[i] == null) {
      var cell = medianValues.shift().cell;
      cell.setGeneralPurposeVariable(rankValue, i);
    }
  }
};

/**
 * Function: medianValue
 *
 * Calculates the median rank order positioning for the specified cell using
 * the connected cells on the specified rank. Returns the median rank
 * ordering value of the connected cells
 *
 * Parameters:
 *
 * connectedCells - the cells on the specified rank connected to the
 * specified cell
 * rankValue - the rank that the connected cell lie upon
 */
mxMedianHybridCrossingReduction.prototype.medianValue = function (connectedCells, rankValue) {
  var medianValues = [];
  var arrayCount = 0;

  for (var i = 0; i < connectedCells.length; i++) {
    var cell = connectedCells[i];
    medianValues[arrayCount++] = cell.getGeneralPurposeVariable(rankValue);
  }

  // Sort() sorts lexicographically by default (i.e. 11 before 9) so force
  // numerical order sort
  medianValues.sort(function (a, b) {
    return a - b;
  });

  if (arrayCount % 2 == 1) {
    // For odd numbers of adjacent vertices return the median
    return medianValues[Math.floor(arrayCount / 2)];
  } else if (arrayCount == 2) {
    return (medianValues[0] + medianValues[1]) / 2.0;
  } else {
    var medianPoint = arrayCount / 2;
    var leftMedian = medianValues[medianPoint - 1] - medianValues[0];
    var rightMedian = medianValues[arrayCount - 1] - medianValues[medianPoint];

    return (
      (medianValues[medianPoint - 1] * rightMedian + medianValues[medianPoint] * leftMedian) /
      (leftMedian + rightMedian)
    );
  }
};

/**
 * Class: MedianCellSorter
 *
 * A utility class used to track cells whilst sorting occurs on the median
 * values. Does not violate (x.compareTo(y)==0) == (x.equals(y))
 *
 * Constructor: MedianCellSorter
 *
 * Constructs a new median cell sorter.
 */
function MedianCellSorter() {
  // empty
}

/**
 * Variable: medianValue
 *
 * The weighted value of the cell stored.
 */
MedianCellSorter.prototype.medianValue = 0;

/**
 * Variable: cell
 *
 * The cell whose median value is being calculated
 */
MedianCellSorter.prototype.cell = false;

/**
 * Function: compare
 *
 * Compares two MedianCellSorters.
 */
MedianCellSorter.prototype.compare = function (a, b) {
  if (a != null && b != null) {
    if (b.medianValue > a.medianValue) {
      return -1;
    } else if (b.medianValue < a.medianValue) {
      return 1;
    } else {
      return 0;
    }
  } else {
    return 0;
  }
};

/**
 * layout/hierarchical/stage/mxMinimumCycleRemover.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxMinimumCycleRemover
 *
 * An implementation of the first stage of the Sugiyama layout. Straightforward
 * longest path calculation of layer assignment
 *
 * Constructor: mxMinimumCycleRemover
 *
 * Creates a cycle remover for the given internal model.
 */
function mxMinimumCycleRemover(layout) {
  this.layout = layout;
}

/**
 * Extends mxHierarchicalLayoutStage.
 */
mxMinimumCycleRemover.prototype = new mxHierarchicalLayoutStage();
mxMinimumCycleRemover.prototype.constructor = mxMinimumCycleRemover;

/**
 * Variable: layout
 *
 * Reference to the enclosing <mxHierarchicalLayout>.
 */
mxMinimumCycleRemover.prototype.layout = null;

/**
 * Function: execute
 *
 * Takes the graph detail and configuration information within the facade
 * and creates the resulting laid out graph within that facade for further
 * use.
 */
mxMinimumCycleRemover.prototype.execute = function (parent) {
  var model = this.layout.getModel();
  var seenNodes = new Object();
  var unseenNodesArray = model.vertexMapper.getValues();
  var unseenNodes = new Object();

  for (var i = 0; i < unseenNodesArray.length; i++) {
    unseenNodes[unseenNodesArray[i].id] = unseenNodesArray[i];
  }

  // Perform a dfs through the internal model. If a cycle is found,
  // reverse it.
  var rootsArray = null;

  if (model.roots != null) {
    var modelRoots = model.roots;
    rootsArray = [];

    for (var i = 0; i < modelRoots.length; i++) {
      rootsArray[i] = model.vertexMapper.get(modelRoots[i]);
    }
  }

  model.visit(
    function (parent, node, connectingEdge, layer, seen) {
      // Check if the cell is in it's own ancestor list, if so
      // invert the connecting edge and reverse the target/source
      // relationship to that edge in the parent and the cell
      if (node.isAncestor(parent)) {
        connectingEdge.invert();
        mxUtils.remove(connectingEdge, parent.connectsAsSource);
        parent.connectsAsTarget.push(connectingEdge);
        mxUtils.remove(connectingEdge, node.connectsAsTarget);
        node.connectsAsSource.push(connectingEdge);
      }

      seenNodes[node.id] = node;
      delete unseenNodes[node.id];
    },
    rootsArray,
    true,
    null
  );

  // If there are any nodes that should be nodes that the dfs can miss
  // these need to be processed with the dfs and the roots assigned
  // correctly to form a correct internal model
  var seenNodesCopy = mxUtils.clone(seenNodes, null, true);

  // Pick a random cell and dfs from it
  model.visit(
    function (parent, node, connectingEdge, layer, seen) {
      // Check if the cell is in it's own ancestor list, if so
      // invert the connecting edge and reverse the target/source
      // relationship to that edge in the parent and the cell
      if (node.isAncestor(parent)) {
        connectingEdge.invert();
        mxUtils.remove(connectingEdge, parent.connectsAsSource);
        node.connectsAsSource.push(connectingEdge);
        parent.connectsAsTarget.push(connectingEdge);
        mxUtils.remove(connectingEdge, node.connectsAsTarget);
      }

      seenNodes[node.id] = node;
      delete unseenNodes[node.id];
    },
    unseenNodes,
    true,
    seenNodesCopy
  );
};

/**
 * layout/hierarchical/stage/mxCoordinateAssignment.js
 */
/**
 * Copyright (c) 2006-2018, JGraph Ltd
 * Copyright (c) 2006-2018, Gaudenz Alder
 */
/**
 * Class: mxCoordinateAssignment
 *
 * Sets the horizontal locations of node and edge dummy nodes on each layer.
 * Uses median down and up weighings as well as heuristics to straighten edges as
 * far as possible.
 *
 * Constructor: mxCoordinateAssignment
 *
 * Creates a coordinate assignment.
 *
 * Arguments:
 *
 * intraCellSpacing - the minimum buffer between cells on the same rank
 * interRankCellSpacing - the minimum distance between cells on adjacent ranks
 * orientation - the position of the root node(s) relative to the graph
 * initialX - the leftmost coordinate node placement starts at
 */
function mxCoordinateAssignment(
  layout,
  intraCellSpacing,
  interRankCellSpacing,
  orientation,
  initialX,
  parallelEdgeSpacing
) {
  this.layout = layout;
  this.intraCellSpacing = intraCellSpacing;
  this.interRankCellSpacing = interRankCellSpacing;
  this.orientation = orientation;
  this.initialX = initialX;
  this.parallelEdgeSpacing = parallelEdgeSpacing;
}

/**
 * Extends mxHierarchicalLayoutStage.
 */
mxCoordinateAssignment.prototype = new mxHierarchicalLayoutStage();
mxCoordinateAssignment.prototype.constructor = mxCoordinateAssignment;

/**
 * Variable: layout
 *
 * Reference to the enclosing <mxHierarchicalLayout>.
 */
mxCoordinateAssignment.prototype.layout = null;

/**
 * Variable: intraCellSpacing
 *
 * The minimum buffer between cells on the same rank. Default is 30.
 */
mxCoordinateAssignment.prototype.intraCellSpacing = 30;

/**
 * Variable: interRankCellSpacing
 *
 * The minimum distance between cells on adjacent ranks. Default is 100.
 */
mxCoordinateAssignment.prototype.interRankCellSpacing = 100;

/**
 * Variable: parallelEdgeSpacing
 *
 * The distance between each parallel edge on each ranks for long edges.
 * Default is 10.
 */
mxCoordinateAssignment.prototype.parallelEdgeSpacing = 10;

/**
 * Variable: maxIterations
 *
 * The number of heuristic iterations to run. Default is 8.
 */
mxCoordinateAssignment.prototype.maxIterations = 8;

/**
 * Variable: prefHozEdgeSep
 *
 * The preferred horizontal distance between edges exiting a vertex Default is 5.
 */
mxCoordinateAssignment.prototype.prefHozEdgeSep = 5;

/**
 * Variable: prefVertEdgeOff
 *
 * The preferred vertical offset between edges exiting a vertex Default is 2.
 */
mxCoordinateAssignment.prototype.prefVertEdgeOff = 2;

/**
 * Variable: minEdgeJetty
 *
 * The minimum distance for an edge jetty from a vertex Default is 12.
 */
mxCoordinateAssignment.prototype.minEdgeJetty = 12;

/**
 * Variable: channelBuffer
 *
 * The size of the vertical buffer in the center of inter-rank channels
 * where edge control points should not be placed Default is 4.
 */
mxCoordinateAssignment.prototype.channelBuffer = 4;

/**
 * Variable: jettyPositions
 *
 * Map of internal edges and (x,y) pair of positions of the start and end jetty
 * for that edge where it connects to the source and target vertices.
 * Note this should technically be a WeakHashMap, but since JS does not
 * have an equivalent, housekeeping must be performed before using.
 * i.e. check all edges are still in the model and clear the values.
 * Note that the y co-ord is the offset of the jetty, not the
 * absolute point
 */
mxCoordinateAssignment.prototype.jettyPositions = null;

/**
 * Variable: orientation
 *
 * The position of the root ( start ) node(s) relative to the rest of the
 * laid out graph. Default is <mxConstants.DIRECTION_NORTH>.
 */
mxCoordinateAssignment.prototype.orientation = mxConstants.DIRECTION_NORTH;

/**
 * Variable: initialX
 *
 * The minimum x position node placement starts at
 */
mxCoordinateAssignment.prototype.initialX = null;

/**
 * Variable: limitX
 *
 * The maximum x value this positioning lays up to
 */
mxCoordinateAssignment.prototype.limitX = null;

/**
 * Variable: currentXDelta
 *
 * The sum of x-displacements for the current iteration
 */
mxCoordinateAssignment.prototype.currentXDelta = null;

/**
 * Variable: widestRank
 *
 * The rank that has the widest x position
 */
mxCoordinateAssignment.prototype.widestRank = null;

/**
 * Variable: rankTopY
 *
 * Internal cache of top-most values of Y for each rank
 */
mxCoordinateAssignment.prototype.rankTopY = null;

/**
 * Variable: rankBottomY
 *
 * Internal cache of bottom-most value of Y for each rank
 */
mxCoordinateAssignment.prototype.rankBottomY = null;

/**
 * Variable: widestRankValue
 *
 * The X-coordinate of the edge of the widest rank
 */
mxCoordinateAssignment.prototype.widestRankValue = null;

/**
 * Variable: rankWidths
 *
 * The width of all the ranks
 */
mxCoordinateAssignment.prototype.rankWidths = null;

/**
 * Variable: rankY
 *
 * The Y-coordinate of all the ranks
 */
mxCoordinateAssignment.prototype.rankY = null;

/**
 * Variable: fineTuning
 *
 * Whether or not to perform local optimisations and iterate multiple times
 * through the algorithm. Default is true.
 */
mxCoordinateAssignment.prototype.fineTuning = true;

/**
 * Variable: nextLayerConnectedCache
 *
 * A store of connections to the layer above for speed
 */
mxCoordinateAssignment.prototype.nextLayerConnectedCache = null;

/**
 * Variable: previousLayerConnectedCache
 *
 * A store of connections to the layer below for speed
 */
mxCoordinateAssignment.prototype.previousLayerConnectedCache = null;

/**
 * Variable: groupPadding
 *
 * Padding added to resized parents Default is 10.
 */
mxCoordinateAssignment.prototype.groupPadding = 10;

/**
 * Utility method to display current positions
 */
mxCoordinateAssignment.prototype.printStatus = function () {
  var model = this.layout.getModel();
  mxLog.show();

  mxLog.writeln('======Coord assignment debug=======');

  for (var j = 0; j < model.ranks.length; j++) {
    mxLog.write('Rank ', j, ' : ');
    var rank = model.ranks[j];

    for (var k = 0; k < rank.length; k++) {
      var cell = rank[k];

      mxLog.write(cell.getGeneralPurposeVariable(j), '  ');
    }
    mxLog.writeln();
  }

  mxLog.writeln('====================================');
};

/**
 * Function: execute
 *
 * A basic horizontal coordinate assignment algorithm
 */
mxCoordinateAssignment.prototype.execute = function (parent) {
  this.jettyPositions = Object();
  var model = this.layout.getModel();
  this.currentXDelta = 0.0;

  this.initialCoords(this.layout.getGraph(), model);

  //	this.printStatus();

  if (this.fineTuning) {
    this.minNode(model);
  }

  var bestXDelta = 100000000.0;

  if (this.fineTuning) {
    for (var i = 0; i < this.maxIterations; i++) {
      //			this.printStatus();

      // Median Heuristic
      if (i != 0) {
        this.medianPos(i, model);
        this.minNode(model);
      }

      // if the total offset is less for the current positioning,
      // there are less heavily angled edges and so the current
      // positioning is used
      if (this.currentXDelta < bestXDelta) {
        for (var j = 0; j < model.ranks.length; j++) {
          var rank = model.ranks[j];

          for (var k = 0; k < rank.length; k++) {
            var cell = rank[k];
            cell.setX(j, cell.getGeneralPurposeVariable(j));
          }
        }

        bestXDelta = this.currentXDelta;
      } else {
        // Restore the best positions
        for (var j = 0; j < model.ranks.length; j++) {
          var rank = model.ranks[j];

          for (var k = 0; k < rank.length; k++) {
            var cell = rank[k];
            cell.setGeneralPurposeVariable(j, cell.getX(j));
          }
        }
      }

      this.minPath(this.layout.getGraph(), model);

      this.currentXDelta = 0;
    }
  }

  this.setCellLocations(this.layout.getGraph(), model);
};

/**
 * Function: minNode
 *
 * Performs one median positioning sweep in both directions
 */
mxCoordinateAssignment.prototype.minNode = function (model) {
  // Queue all nodes
  var nodeList = [];

  // Need to be able to map from cell to cellWrapper
  var map = new mxDictionary();
  var rank = [];

  for (var i = 0; i <= model.maxRank; i++) {
    rank[i] = model.ranks[i];

    for (var j = 0; j < rank[i].length; j++) {
      // Use the weight to store the rank and visited to store whether
      // or not the cell is in the list
      var node = rank[i][j];
      var nodeWrapper = new WeightedCellSorter(node, i);
      nodeWrapper.rankIndex = j;
      nodeWrapper.visited = true;
      nodeList.push(nodeWrapper);

      map.put(node, nodeWrapper);
    }
  }

  // Set a limit of the maximum number of times we will access the queue
  // in case a loop appears
  var maxTries = nodeList.length * 10;
  var count = 0;

  // Don't move cell within this value of their median
  var tolerance = 1;

  while (nodeList.length > 0 && count <= maxTries) {
    var cellWrapper = nodeList.shift();
    var cell = cellWrapper.cell;

    var rankValue = cellWrapper.weightedValue;
    var rankIndex = parseInt(cellWrapper.rankIndex);

    var nextLayerConnectedCells = cell.getNextLayerConnectedCells(rankValue);
    var previousLayerConnectedCells = cell.getPreviousLayerConnectedCells(rankValue);

    var numNextLayerConnected = nextLayerConnectedCells.length;
    var numPreviousLayerConnected = previousLayerConnectedCells.length;

    var medianNextLevel = this.medianXValue(nextLayerConnectedCells, rankValue + 1);
    var medianPreviousLevel = this.medianXValue(previousLayerConnectedCells, rankValue - 1);

    var numConnectedNeighbours = numNextLayerConnected + numPreviousLayerConnected;
    var currentPosition = cell.getGeneralPurposeVariable(rankValue);
    var cellMedian = currentPosition;

    if (numConnectedNeighbours > 0) {
      cellMedian =
        (medianNextLevel * numNextLayerConnected + medianPreviousLevel * numPreviousLayerConnected) /
        numConnectedNeighbours;
    }

    // Flag storing whether or not position has changed
    var positionChanged = false;

    if (cellMedian < currentPosition - tolerance) {
      if (rankIndex == 0) {
        cell.setGeneralPurposeVariable(rankValue, cellMedian);
        positionChanged = true;
      } else {
        var leftCell = rank[rankValue][rankIndex - 1];
        var leftLimit = leftCell.getGeneralPurposeVariable(rankValue);
        leftLimit = leftLimit + leftCell.width / 2 + this.intraCellSpacing + cell.width / 2;

        if (leftLimit < cellMedian) {
          cell.setGeneralPurposeVariable(rankValue, cellMedian);
          positionChanged = true;
        } else if (leftLimit < cell.getGeneralPurposeVariable(rankValue) - tolerance) {
          cell.setGeneralPurposeVariable(rankValue, leftLimit);
          positionChanged = true;
        }
      }
    } else if (cellMedian > currentPosition + tolerance) {
      var rankSize = rank[rankValue].length;

      if (rankIndex == rankSize - 1) {
        cell.setGeneralPurposeVariable(rankValue, cellMedian);
        positionChanged = true;
      } else {
        var rightCell = rank[rankValue][rankIndex + 1];
        var rightLimit = rightCell.getGeneralPurposeVariable(rankValue);
        rightLimit = rightLimit - rightCell.width / 2 - this.intraCellSpacing - cell.width / 2;

        if (rightLimit > cellMedian) {
          cell.setGeneralPurposeVariable(rankValue, cellMedian);
          positionChanged = true;
        } else if (rightLimit > cell.getGeneralPurposeVariable(rankValue) + tolerance) {
          cell.setGeneralPurposeVariable(rankValue, rightLimit);
          positionChanged = true;
        }
      }
    }

    if (positionChanged) {
      // Add connected nodes to map and list
      for (var i = 0; i < nextLayerConnectedCells.length; i++) {
        var connectedCell = nextLayerConnectedCells[i];
        var connectedCellWrapper = map.get(connectedCell);

        if (connectedCellWrapper != null) {
          if (connectedCellWrapper.visited == false) {
            connectedCellWrapper.visited = true;
            nodeList.push(connectedCellWrapper);
          }
        }
      }

      // Add connected nodes to map and list
      for (var i = 0; i < previousLayerConnectedCells.length; i++) {
        var connectedCell = previousLayerConnectedCells[i];
        var connectedCellWrapper = map.get(connectedCell);

        if (connectedCellWrapper != null) {
          if (connectedCellWrapper.visited == false) {
            connectedCellWrapper.visited = true;
            nodeList.push(connectedCellWrapper);
          }
        }
      }
    }

    cellWrapper.visited = false;
    count++;
  }
};

/**
 * Function: medianPos
 *
 * Performs one median positioning sweep in one direction
 *
 * Parameters:
 *
 * i - the iteration of the whole process
 * model - an internal model of the hierarchical layout
 */
mxCoordinateAssignment.prototype.medianPos = function (i, model) {
  // Reverse sweep direction each time through this method
  var downwardSweep = i % 2 == 0;

  if (downwardSweep) {
    for (var j = model.maxRank; j > 0; j--) {
      this.rankMedianPosition(j - 1, model, j);
    }
  } else {
    for (var j = 0; j < model.maxRank - 1; j++) {
      this.rankMedianPosition(j + 1, model, j);
    }
  }
};

/**
 * Function: rankMedianPosition
 *
 * Performs median minimisation over one rank.
 *
 * Parameters:
 *
 * rankValue - the layer number of this rank
 * model - an internal model of the hierarchical layout
 * nextRankValue - the layer number whose connected cels are to be laid out
 * relative to
 */
mxCoordinateAssignment.prototype.rankMedianPosition = function (rankValue, model, nextRankValue) {
  var rank = model.ranks[rankValue];

  // Form an array of the order in which the cell are to be processed
  // , the order is given by the weighted sum of the in or out edges,
  // depending on whether we're traveling up or down the hierarchy.
  var weightedValues = [];
  var cellMap = new Object();

  for (var i = 0; i < rank.length; i++) {
    var currentCell = rank[i];
    weightedValues[i] = new WeightedCellSorter();
    weightedValues[i].cell = currentCell;
    weightedValues[i].rankIndex = i;
    cellMap[currentCell.id] = weightedValues[i];
    var nextLayerConnectedCells = null;

    if (nextRankValue < rankValue) {
      nextLayerConnectedCells = currentCell.getPreviousLayerConnectedCells(rankValue);
    } else {
      nextLayerConnectedCells = currentCell.getNextLayerConnectedCells(rankValue);
    }

    // Calculate the weighing based on this node type and those this
    // node is connected to on the next layer
    weightedValues[i].weightedValue = this.calculatedWeightedValue(currentCell, nextLayerConnectedCells);
  }

  weightedValues.sort(WeightedCellSorter.prototype.compare);

  // Set the new position of each node within the rank using
  // its temp variable

  for (var i = 0; i < weightedValues.length; i++) {
    var numConnectionsNextLevel = 0;
    var cell = weightedValues[i].cell;
    var nextLayerConnectedCells = null;
    var medianNextLevel = 0;

    if (nextRankValue < rankValue) {
      nextLayerConnectedCells = cell.getPreviousLayerConnectedCells(rankValue).slice();
    } else {
      nextLayerConnectedCells = cell.getNextLayerConnectedCells(rankValue).slice();
    }

    if (nextLayerConnectedCells != null) {
      numConnectionsNextLevel = nextLayerConnectedCells.length;

      if (numConnectionsNextLevel > 0) {
        medianNextLevel = this.medianXValue(nextLayerConnectedCells, nextRankValue);
      } else {
        // For case of no connections on the next level set the
        // median to be the current position and try to be
        // positioned there
        medianNextLevel = cell.getGeneralPurposeVariable(rankValue);
      }
    }

    var leftBuffer = 0.0;
    var leftLimit = -100000000.0;

    for (var j = weightedValues[i].rankIndex - 1; j >= 0; ) {
      var weightedValue = cellMap[rank[j].id];

      if (weightedValue != null) {
        var leftCell = weightedValue.cell;

        if (weightedValue.visited) {
          // The left limit is the right hand limit of that
          // cell plus any allowance for unallocated cells
          // in-between
          leftLimit =
            leftCell.getGeneralPurposeVariable(rankValue) +
            leftCell.width / 2.0 +
            this.intraCellSpacing +
            leftBuffer +
            cell.width / 2.0;
          j = -1;
        } else {
          leftBuffer += leftCell.width + this.intraCellSpacing;
          j--;
        }
      }
    }

    var rightBuffer = 0.0;
    var rightLimit = 100000000.0;

    for (var j = weightedValues[i].rankIndex + 1; j < weightedValues.length; ) {
      var weightedValue = cellMap[rank[j].id];

      if (weightedValue != null) {
        var rightCell = weightedValue.cell;

        if (weightedValue.visited) {
          // The left limit is the right hand limit of that
          // cell plus any allowance for unallocated cells
          // in-between
          rightLimit =
            rightCell.getGeneralPurposeVariable(rankValue) -
            rightCell.width / 2.0 -
            this.intraCellSpacing -
            rightBuffer -
            cell.width / 2.0;
          j = weightedValues.length;
        } else {
          rightBuffer += rightCell.width + this.intraCellSpacing;
          j++;
        }
      }
    }

    if (medianNextLevel >= leftLimit && medianNextLevel <= rightLimit) {
      cell.setGeneralPurposeVariable(rankValue, medianNextLevel);
    } else if (medianNextLevel < leftLimit) {
      // Couldn't place at median value, place as close to that
      // value as possible
      cell.setGeneralPurposeVariable(rankValue, leftLimit);
      this.currentXDelta += leftLimit - medianNextLevel;
    } else if (medianNextLevel > rightLimit) {
      // Couldn't place at median value, place as close to that
      // value as possible
      cell.setGeneralPurposeVariable(rankValue, rightLimit);
      this.currentXDelta += medianNextLevel - rightLimit;
    }

    weightedValues[i].visited = true;
  }
};

/**
 * Function: calculatedWeightedValue
 *
 * Calculates the priority the specified cell has based on the type of its
 * cell and the cells it is connected to on the next layer
 *
 * Parameters:
 *
 * currentCell - the cell whose weight is to be calculated
 * collection - the cells the specified cell is connected to
 */
mxCoordinateAssignment.prototype.calculatedWeightedValue = function (currentCell, collection) {
  var totalWeight = 0;

  for (var i = 0; i < collection.length; i++) {
    var cell = collection[i];

    if (currentCell.isVertex() && cell.isVertex()) {
      totalWeight++;
    } else if (currentCell.isEdge() && cell.isEdge()) {
      totalWeight += 8;
    } else {
      totalWeight += 2;
    }
  }

  return totalWeight;
};

/**
 * Function: medianXValue
 *
 * Calculates the median position of the connected cell on the specified
 * rank
 *
 * Parameters:
 *
 * connectedCells - the cells the candidate connects to on this level
 * rankValue - the layer number of this rank
 */
mxCoordinateAssignment.prototype.medianXValue = function (connectedCells, rankValue) {
  if (connectedCells.length == 0) {
    return 0;
  }

  var medianValues = [];

  for (var i = 0; i < connectedCells.length; i++) {
    medianValues[i] = connectedCells[i].getGeneralPurposeVariable(rankValue);
  }

  medianValues.sort(function (a, b) {
    return a - b;
  });

  if (connectedCells.length % 2 == 1) {
    // For odd numbers of adjacent vertices return the median
    return medianValues[Math.floor(connectedCells.length / 2)];
  } else {
    var medianPoint = connectedCells.length / 2;
    var leftMedian = medianValues[medianPoint - 1];
    var rightMedian = medianValues[medianPoint];

    return (leftMedian + rightMedian) / 2;
  }
};

/**
 * Function: initialCoords
 *
 * Sets up the layout in an initial positioning. The ranks are all centered
 * as much as possible along the middle vertex in each rank. The other cells
 * are then placed as close as possible on either side.
 *
 * Parameters:
 *
 * facade - the facade describing the input graph
 * model - an internal model of the hierarchical layout
 */
mxCoordinateAssignment.prototype.initialCoords = function (facade, model) {
  this.calculateWidestRank(facade, model);

  // Sweep up and down from the widest rank
  for (var i = this.widestRank; i >= 0; i--) {
    if (i < model.maxRank) {
      this.rankCoordinates(i, facade, model);
    }
  }

  for (var i = this.widestRank + 1; i <= model.maxRank; i++) {
    if (i > 0) {
      this.rankCoordinates(i, facade, model);
    }
  }
};

/**
 * Function: rankCoordinates
 *
 * Sets up the layout in an initial positioning. All the first cells in each
 * rank are moved to the left and the rest of the rank inserted as close
 * together as their size and buffering permits. This method works on just
 * the specified rank.
 *
 * Parameters:
 *
 * rankValue - the current rank being processed
 * graph - the facade describing the input graph
 * model - an internal model of the hierarchical layout
 */
mxCoordinateAssignment.prototype.rankCoordinates = function (rankValue, graph, model) {
  var rank = model.ranks[rankValue];
  var maxY = 0.0;
  var localX = this.initialX + (this.widestRankValue - this.rankWidths[rankValue]) / 2;

  // Store whether or not any of the cells' bounds were unavailable so
  // to only issue the warning once for all cells
  var boundsWarning = false;

  for (var i = 0; i < rank.length; i++) {
    var node = rank[i];

    if (node.isVertex()) {
      var bounds = this.layout.getVertexBounds(node.cell);

      if (bounds != null) {
        if (this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH) {
          node.width = bounds.width;
          node.height = bounds.height;
        } else {
          node.width = bounds.height;
          node.height = bounds.width;
        }
      } else {
        boundsWarning = true;
      }

      maxY = Math.max(maxY, node.height);
    } else if (node.isEdge()) {
      // The width is the number of additional parallel edges
      // time the parallel edge spacing
      var numEdges = 1;

      if (node.edges != null) {
        numEdges = node.edges.length;
      } else {
        mxLog.warn('edge.edges is null');
      }

      node.width = (numEdges - 1) * this.parallelEdgeSpacing;
    }

    // Set the initial x-value as being the best result so far
    localX += node.width / 2.0;
    node.setX(rankValue, localX);
    node.setGeneralPurposeVariable(rankValue, localX);
    localX += node.width / 2.0;
    localX += this.intraCellSpacing;
  }

  if (boundsWarning == true) {
    mxLog.warn('At least one cell has no bounds');
  }
};

/**
 * Function: calculateWidestRank
 *
 * Calculates the width rank in the hierarchy. Also set the y value of each
 * rank whilst performing the calculation
 *
 * Parameters:
 *
 * graph - the facade describing the input graph
 * model - an internal model of the hierarchical layout
 */
mxCoordinateAssignment.prototype.calculateWidestRank = function (graph, model) {
  // Starting y co-ordinate
  var y = -this.interRankCellSpacing;

  // Track the widest cell on the last rank since the y
  // difference depends on it
  var lastRankMaxCellHeight = 0.0;
  this.rankWidths = [];
  this.rankY = [];

  for (var rankValue = model.maxRank; rankValue >= 0; rankValue--) {
    // Keep track of the widest cell on this rank
    var maxCellHeight = 0.0;
    var rank = model.ranks[rankValue];
    var localX = this.initialX;

    // Store whether or not any of the cells' bounds were unavailable so
    // to only issue the warning once for all cells
    var boundsWarning = false;

    for (var i = 0; i < rank.length; i++) {
      var node = rank[i];

      if (node.isVertex()) {
        var bounds = this.layout.getVertexBounds(node.cell);

        if (bounds != null) {
          if (this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH) {
            node.width = bounds.width;
            node.height = bounds.height;
          } else {
            node.width = bounds.height;
            node.height = bounds.width;
          }
        } else {
          boundsWarning = true;
        }

        maxCellHeight = Math.max(maxCellHeight, node.height);
      } else if (node.isEdge()) {
        // The width is the number of additional parallel edges
        // time the parallel edge spacing
        var numEdges = 1;

        if (node.edges != null) {
          numEdges = node.edges.length;
        } else {
          mxLog.warn('edge.edges is null');
        }

        node.width = (numEdges - 1) * this.parallelEdgeSpacing;
      }

      // Set the initial x-value as being the best result so far
      localX += node.width / 2.0;
      node.setX(rankValue, localX);
      node.setGeneralPurposeVariable(rankValue, localX);
      localX += node.width / 2.0;
      localX += this.intraCellSpacing;

      if (localX > this.widestRankValue) {
        this.widestRankValue = localX;
        this.widestRank = rankValue;
      }

      this.rankWidths[rankValue] = localX;
    }

    if (boundsWarning == true) {
      mxLog.warn('At least one cell has no bounds');
    }

    this.rankY[rankValue] = y;
    var distanceToNextRank = maxCellHeight / 2.0 + lastRankMaxCellHeight / 2.0 + this.interRankCellSpacing;
    lastRankMaxCellHeight = maxCellHeight;

    if (this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_WEST) {
      y += distanceToNextRank;
    } else {
      y -= distanceToNextRank;
    }

    for (var i = 0; i < rank.length; i++) {
      var cell = rank[i];
      cell.setY(rankValue, y);
    }
  }
};

/**
 * Function: minPath
 *
 * Straightens out chains of virtual nodes where possibleacade to those stored after this layout
 * processing step has completed.
 *
 * Parameters:
 *
 * graph - the facade describing the input graph
 * model - an internal model of the hierarchical layout
 */
mxCoordinateAssignment.prototype.minPath = function (graph, model) {
  // Work down and up each edge with at least 2 control points
  // trying to straighten each one out. If the same number of
  // straight segments are formed in both directions, the
  // preferred direction used is the one where the final
  // control points have the least offset from the connectable
  // region of the terminating vertices
  var edges = model.edgeMapper.getValues();

  for (var j = 0; j < edges.length; j++) {
    var cell = edges[j];

    if (cell.maxRank - cell.minRank - 1 < 1) {
      continue;
    }

    // At least two virtual nodes in the edge
    // Check first whether the edge is already straight
    var referenceX = cell.getGeneralPurposeVariable(cell.minRank + 1);
    var edgeStraight = true;
    var refSegCount = 0;

    for (var i = cell.minRank + 2; i < cell.maxRank; i++) {
      var x = cell.getGeneralPurposeVariable(i);

      if (referenceX != x) {
        edgeStraight = false;
        referenceX = x;
      } else {
        refSegCount++;
      }
    }

    if (!edgeStraight) {
      var upSegCount = 0;
      var downSegCount = 0;
      var upXPositions = [];
      var downXPositions = [];

      var currentX = cell.getGeneralPurposeVariable(cell.minRank + 1);

      for (var i = cell.minRank + 1; i < cell.maxRank - 1; i++) {
        // Attempt to straight out the control point on the
        // next segment up with the current control point.
        var nextX = cell.getX(i + 1);

        if (currentX == nextX) {
          upXPositions[i - cell.minRank - 1] = currentX;
          upSegCount++;
        } else if (this.repositionValid(model, cell, i + 1, currentX)) {
          upXPositions[i - cell.minRank - 1] = currentX;
          upSegCount++;
          // Leave currentX at same value
        } else {
          upXPositions[i - cell.minRank - 1] = nextX;
          currentX = nextX;
        }
      }

      currentX = cell.getX(i);

      for (var i = cell.maxRank - 1; i > cell.minRank + 1; i--) {
        // Attempt to straight out the control point on the
        // next segment down with the current control point.
        var nextX = cell.getX(i - 1);

        if (currentX == nextX) {
          downXPositions[i - cell.minRank - 2] = currentX;
          downSegCount++;
        } else if (this.repositionValid(model, cell, i - 1, currentX)) {
          downXPositions[i - cell.minRank - 2] = currentX;
          downSegCount++;
          // Leave currentX at same value
        } else {
          downXPositions[i - cell.minRank - 2] = cell.getX(i - 1);
          currentX = nextX;
        }
      }

      if (downSegCount > refSegCount || upSegCount > refSegCount) {
        if (downSegCount >= upSegCount) {
          // Apply down calculation values
          for (var i = cell.maxRank - 2; i > cell.minRank; i--) {
            cell.setX(i, downXPositions[i - cell.minRank - 1]);
          }
        } else if (upSegCount > downSegCount) {
          // Apply up calculation values
          for (var i = cell.minRank + 2; i < cell.maxRank; i++) {
            cell.setX(i, upXPositions[i - cell.minRank - 2]);
          }
        } else {
          // Neither direction provided a favourable result
          // But both calculations are better than the
          // existing solution, so apply the one with minimal
          // offset to attached vertices at either end.
        }
      }
    }
  }
};

/**
 * Function: repositionValid
 *
 * Determines whether or not a node may be moved to the specified x
 * position on the specified rank
 *
 * Parameters:
 *
 * model - the layout model
 * cell - the cell being analysed
 * rank - the layer of the cell
 * position - the x position being sought
 */
mxCoordinateAssignment.prototype.repositionValid = function (model, cell, rank, position) {
  var rankArray = model.ranks[rank];
  var rankIndex = -1;

  for (var i = 0; i < rankArray.length; i++) {
    if (cell == rankArray[i]) {
      rankIndex = i;
      break;
    }
  }

  if (rankIndex < 0) {
    return false;
  }

  var currentX = cell.getGeneralPurposeVariable(rank);

  if (position < currentX) {
    // Trying to move node to the left.
    if (rankIndex == 0) {
      // Left-most node, can move anywhere
      return true;
    }

    var leftCell = rankArray[rankIndex - 1];
    var leftLimit = leftCell.getGeneralPurposeVariable(rank);
    leftLimit = leftLimit + leftCell.width / 2 + this.intraCellSpacing + cell.width / 2;

    if (leftLimit <= position) {
      return true;
    } else {
      return false;
    }
  } else if (position > currentX) {
    // Trying to move node to the right.
    if (rankIndex == rankArray.length - 1) {
      // Right-most node, can move anywhere
      return true;
    }

    var rightCell = rankArray[rankIndex + 1];
    var rightLimit = rightCell.getGeneralPurposeVariable(rank);
    rightLimit = rightLimit - rightCell.width / 2 - this.intraCellSpacing - cell.width / 2;

    if (rightLimit >= position) {
      return true;
    } else {
      return false;
    }
  }

  return true;
};

/**
 * Function: setCellLocations
 *
 * Sets the cell locations in the facade to those stored after this layout
 * processing step has completed.
 *
 * Parameters:
 *
 * graph - the input graph
 * model - the layout model
 */
mxCoordinateAssignment.prototype.setCellLocations = function (graph, model) {
  this.rankTopY = [];
  this.rankBottomY = [];

  for (var i = 0; i < model.ranks.length; i++) {
    this.rankTopY[i] = Number.MAX_VALUE;
    this.rankBottomY[i] = -Number.MAX_VALUE;
  }

  var vertices = model.vertexMapper.getValues();

  // Process vertices all first, since they define the lower and
  // limits of each rank. Between these limits lie the channels
  // where the edges can be routed across the graph

  for (var i = 0; i < vertices.length; i++) {
    this.setVertexLocation(vertices[i]);
  }

  // Post process edge styles. Needs the vertex locations set for initial
  // values of the top and bottoms of each rank
  if (
    this.layout.edgeStyle == mxHierarchicalEdgeStyle.ORTHOGONAL ||
    this.layout.edgeStyle == mxHierarchicalEdgeStyle.POLYLINE ||
    this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE
  ) {
    this.localEdgeProcessing(model);
  }

  var edges = model.edgeMapper.getValues();

  for (var i = 0; i < edges.length; i++) {
    this.setEdgePosition(edges[i]);
  }
};

/**
 * Function: localEdgeProcessing
 *
 * Separates the x position of edges as they connect to vertices
 *
 * Parameters:
 *
 * model - the layout model
 */
mxCoordinateAssignment.prototype.localEdgeProcessing = function (model) {
  // Iterate through each vertex, look at the edges connected in
  // both directions.
  for (var rankIndex = 0; rankIndex < model.ranks.length; rankIndex++) {
    var rank = model.ranks[rankIndex];

    for (var cellIndex = 0; cellIndex < rank.length; cellIndex++) {
      var cell = rank[cellIndex];

      if (cell.isVertex()) {
        var currentCells = cell.getPreviousLayerConnectedCells(rankIndex);

        var currentRank = rankIndex - 1;

        // Two loops, last connected cells, and next
        for (var k = 0; k < 2; k++) {
          if (currentRank > -1 && currentRank < model.ranks.length && currentCells != null && currentCells.length > 0) {
            var sortedCells = [];

            for (var j = 0; j < currentCells.length; j++) {
              var sorter = new WeightedCellSorter(currentCells[j], currentCells[j].getX(currentRank));
              sortedCells.push(sorter);
            }

            sortedCells.sort(WeightedCellSorter.prototype.compare);

            var leftLimit = cell.x[0] - cell.width / 2;
            var rightLimit = leftLimit + cell.width;

            // Connected edge count starts at 1 to allow for buffer
            // with edge of vertex
            var connectedEdgeCount = 0;
            var connectedEdgeGroupCount = 0;
            var connectedEdges = [];
            // Calculate width requirements for all connected edges
            for (var j = 0; j < sortedCells.length; j++) {
              var innerCell = sortedCells[j].cell;
              var connections;

              if (innerCell.isVertex()) {
                // Get the connecting edge
                if (k == 0) {
                  connections = cell.connectsAsSource;
                } else {
                  connections = cell.connectsAsTarget;
                }

                for (var connIndex = 0; connIndex < connections.length; connIndex++) {
                  if (connections[connIndex].source == innerCell || connections[connIndex].target == innerCell) {
                    connectedEdgeCount += connections[connIndex].edges.length;
                    connectedEdgeGroupCount++;

                    connectedEdges.push(connections[connIndex]);
                  }
                }
              } else {
                connectedEdgeCount += innerCell.edges.length;
                connectedEdgeGroupCount++;
                connectedEdges.push(innerCell);
              }
            }

            var requiredWidth = (connectedEdgeCount + 1) * this.prefHozEdgeSep;

            // Add a buffer on the edges of the vertex if the edge count allows
            if (cell.width > requiredWidth + 2 * this.prefHozEdgeSep) {
              leftLimit += this.prefHozEdgeSep;
              rightLimit -= this.prefHozEdgeSep;
            }

            var availableWidth = rightLimit - leftLimit;
            var edgeSpacing = availableWidth / connectedEdgeCount;

            var currentX = leftLimit + edgeSpacing / 2.0;
            var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
            var maxYOffset = 0;

            for (var j = 0; j < connectedEdges.length; j++) {
              var numActualEdges = connectedEdges[j].edges.length;
              var pos = this.jettyPositions[connectedEdges[j].ids[0]];

              if (pos == null) {
                pos = [];
                this.jettyPositions[connectedEdges[j].ids[0]] = pos;
              }

              if (j < connectedEdgeCount / 2) {
                currentYOffset += this.prefVertEdgeOff;
              } else if (j > connectedEdgeCount / 2) {
                currentYOffset -= this.prefVertEdgeOff;
              }
              // Ignore the case if equals, this means the second of 2
              // jettys with the same y (even number of edges)

              for (var m = 0; m < numActualEdges; m++) {
                pos[m * 4 + k * 2] = currentX;
                currentX += edgeSpacing;
                pos[m * 4 + k * 2 + 1] = currentYOffset;
              }

              maxYOffset = Math.max(maxYOffset, currentYOffset);
            }
          }

          currentCells = cell.getNextLayerConnectedCells(rankIndex);

          currentRank = rankIndex + 1;
        }
      }
    }
  }
};

/**
 * Function: setEdgePosition
 *
 * Fixes the control points
 */
mxCoordinateAssignment.prototype.setEdgePosition = function (cell) {
  // For parallel edges we need to seperate out the points a
  // little
  var offsetX = 0;
  // Only set the edge control points once

  if (cell.temp[0] != 101207) {
    var maxRank = cell.maxRank;
    var minRank = cell.minRank;

    if (maxRank == minRank) {
      maxRank = cell.source.maxRank;
      minRank = cell.target.minRank;
    }

    var parallelEdgeCount = 0;
    var jettys = this.jettyPositions[cell.ids[0]];

    var source = cell.isReversed ? cell.target.cell : cell.source.cell;
    var graph = this.layout.graph;
    var layoutReversed =
      this.orientation == mxConstants.DIRECTION_EAST || this.orientation == mxConstants.DIRECTION_SOUTH;

    for (var i = 0; i < cell.edges.length; i++) {
      var realEdge = cell.edges[i];
      var realSource = this.layout.getVisibleTerminal(realEdge, true);

      //List oldPoints = graph.getPoints(realEdge);
      var newPoints = [];

      // Single length reversed edges end up with the jettys in the wrong
      // places. Since single length edges only have jettys, not segment
      // control points, we just say the edge isn't reversed in this section
      var reversed = cell.isReversed;

      if (realSource != source) {
        // The real edges include all core model edges and these can go
        // in both directions. If the source of the hierarchical model edge
        // isn't the source of the specific real edge in this iteration
        // treat if as reversed
        reversed = !reversed;
      }

      // First jetty of edge
      if (jettys != null) {
        var arrayOffset = reversed ? 2 : 0;
        var y = reversed
          ? layoutReversed
            ? this.rankBottomY[minRank]
            : this.rankTopY[minRank]
          : layoutReversed
          ? this.rankTopY[maxRank]
          : this.rankBottomY[maxRank];
        var jetty = jettys[parallelEdgeCount * 4 + 1 + arrayOffset];

        if (reversed != layoutReversed) {
          jetty = -jetty;
        }

        y += jetty;
        var x = jettys[parallelEdgeCount * 4 + arrayOffset];

        var modelSource = graph.model.getTerminal(realEdge, true);

        if (this.layout.isPort(modelSource) && graph.model.getParent(modelSource) == realSource) {
          var state = graph.view.getState(modelSource);

          if (state != null) {
            x = state.x;
          } else {
            x = realSource.geometry.x + cell.source.width * modelSource.geometry.x;
          }
        }

        if (this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH) {
          newPoints.push(new mxPoint(x, y));

          if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE) {
            newPoints.push(new mxPoint(x, y + jetty));
          }
        } else {
          newPoints.push(new mxPoint(y, x));

          if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE) {
            newPoints.push(new mxPoint(y + jetty, x));
          }
        }
      }

      // Declare variables to define loop through edge points and
      // change direction if edge is reversed

      var loopStart = cell.x.length - 1;
      var loopLimit = -1;
      var loopDelta = -1;
      var currentRank = cell.maxRank - 1;

      if (reversed) {
        loopStart = 0;
        loopLimit = cell.x.length;
        loopDelta = 1;
        currentRank = cell.minRank + 1;
      }
      // Reversed edges need the points inserted in
      // reverse order
      for (var j = loopStart; cell.maxRank != cell.minRank && j != loopLimit; j += loopDelta) {
        // The horizontal position in a vertical layout
        var positionX = cell.x[j] + offsetX;

        // Work out the vertical positions in a vertical layout
        // in the edge buffer channels above and below this rank
        var topChannelY = (this.rankTopY[currentRank] + this.rankBottomY[currentRank + 1]) / 2.0;
        var bottomChannelY = (this.rankTopY[currentRank - 1] + this.rankBottomY[currentRank]) / 2.0;

        if (reversed) {
          var tmp = topChannelY;
          topChannelY = bottomChannelY;
          bottomChannelY = tmp;
        }

        if (this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH) {
          newPoints.push(new mxPoint(positionX, topChannelY));
          newPoints.push(new mxPoint(positionX, bottomChannelY));
        } else {
          newPoints.push(new mxPoint(topChannelY, positionX));
          newPoints.push(new mxPoint(bottomChannelY, positionX));
        }

        this.limitX = Math.max(this.limitX, positionX);
        currentRank += loopDelta;
      }

      // Second jetty of edge
      if (jettys != null) {
        var arrayOffset = reversed ? 2 : 0;
        var rankY = reversed
          ? layoutReversed
            ? this.rankTopY[maxRank]
            : this.rankBottomY[maxRank]
          : layoutReversed
          ? this.rankBottomY[minRank]
          : this.rankTopY[minRank];
        var jetty = jettys[parallelEdgeCount * 4 + 3 - arrayOffset];

        if (reversed != layoutReversed) {
          jetty = -jetty;
        }
        var y = rankY - jetty;
        var x = jettys[parallelEdgeCount * 4 + 2 - arrayOffset];

        var modelTarget = graph.model.getTerminal(realEdge, false);
        var realTarget = this.layout.getVisibleTerminal(realEdge, false);

        if (this.layout.isPort(modelTarget) && graph.model.getParent(modelTarget) == realTarget) {
          var state = graph.view.getState(modelTarget);

          if (state != null) {
            x = state.x;
          } else {
            x = realTarget.geometry.x + cell.target.width * modelTarget.geometry.x;
          }
        }

        if (this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH) {
          if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE) {
            newPoints.push(new mxPoint(x, y - jetty));
          }

          newPoints.push(new mxPoint(x, y));
        } else {
          if (this.layout.edgeStyle == mxHierarchicalEdgeStyle.CURVE) {
            newPoints.push(new mxPoint(y - jetty, x));
          }

          newPoints.push(new mxPoint(y, x));
        }
      }

      if (cell.isReversed) {
        this.processReversedEdge(cell, realEdge);
      }

      this.layout.setEdgePoints(realEdge, newPoints);

      // Increase offset so next edge is drawn next to
      // this one
      if (offsetX == 0.0) {
        offsetX = this.parallelEdgeSpacing;
      } else if (offsetX > 0) {
        offsetX = -offsetX;
      } else {
        offsetX = -offsetX + this.parallelEdgeSpacing;
      }

      parallelEdgeCount++;
    }

    cell.temp[0] = 101207;
  }
};

/**
 * Function: setVertexLocation
 *
 * Fixes the position of the specified vertex.
 *
 * Parameters:
 *
 * cell - the vertex to position
 */
mxCoordinateAssignment.prototype.setVertexLocation = function (cell) {
  var realCell = cell.cell;
  var positionX = cell.x[0] - cell.width / 2;
  var positionY = cell.y[0] - cell.height / 2;

  this.rankTopY[cell.minRank] = Math.min(this.rankTopY[cell.minRank], positionY);
  this.rankBottomY[cell.minRank] = Math.max(this.rankBottomY[cell.minRank], positionY + cell.height);

  if (this.orientation == mxConstants.DIRECTION_NORTH || this.orientation == mxConstants.DIRECTION_SOUTH) {
    this.layout.setVertexLocation(realCell, positionX, positionY);
  } else {
    this.layout.setVertexLocation(realCell, positionY, positionX);
  }

  this.limitX = Math.max(this.limitX, positionX + cell.width);
};

/**
 * Function: processReversedEdge
 *
 * Hook to add additional processing
 *
 * Parameters:
 *
 * edge - the hierarchical model edge
 * realEdge - the real edge in the graph
 */
mxCoordinateAssignment.prototype.processReversedEdge = function (graph, model) {
  // hook for subclassers
};

/**
 * layout/hierarchical/stage/mxSwimlaneOrdering.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxSwimlaneOrdering
 *
 * An implementation of the first stage of the Sugiyama layout. Straightforward
 * longest path calculation of layer assignment
 *
 * Constructor: mxSwimlaneOrdering
 *
 * Creates a cycle remover for the given internal model.
 */
function mxSwimlaneOrdering(layout) {
  this.layout = layout;
}

/**
 * Extends mxHierarchicalLayoutStage.
 */
mxSwimlaneOrdering.prototype = new mxHierarchicalLayoutStage();
mxSwimlaneOrdering.prototype.constructor = mxSwimlaneOrdering;

/**
 * Variable: layout
 *
 * Reference to the enclosing <mxHierarchicalLayout>.
 */
mxSwimlaneOrdering.prototype.layout = null;

/**
 * Function: execute
 *
 * Takes the graph detail and configuration information within the facade
 * and creates the resulting laid out graph within that facade for further
 * use.
 */
mxSwimlaneOrdering.prototype.execute = function (parent) {
  var model = this.layout.getModel();
  var seenNodes = new Object();
  var unseenNodes = mxUtils.clone(model.vertexMapper, null, true);

  // Perform a dfs through the internal model. If a cycle is found,
  // reverse it.
  var rootsArray = null;

  if (model.roots != null) {
    var modelRoots = model.roots;
    rootsArray = [];

    for (var i = 0; i < modelRoots.length; i++) {
      rootsArray[i] = model.vertexMapper.get(modelRoots[i]);
    }
  }

  model.visit(
    function (parent, node, connectingEdge, layer, seen) {
      // Check if the cell is in it's own ancestor list, if so
      // invert the connecting edge and reverse the target/source
      // relationship to that edge in the parent and the cell
      // Ancestor hashes only line up within a swimlane
      var isAncestor = parent != null && parent.swimlaneIndex == node.swimlaneIndex && node.isAncestor(parent);

      // If the source->target swimlane indices go from higher to
      // lower, the edge is reverse
      var reversedOverSwimlane =
        parent != null &&
        connectingEdge != null &&
        parent.swimlaneIndex < node.swimlaneIndex &&
        connectingEdge.source == node;

      if (isAncestor) {
        connectingEdge.invert();
        mxUtils.remove(connectingEdge, parent.connectsAsSource);
        node.connectsAsSource.push(connectingEdge);
        parent.connectsAsTarget.push(connectingEdge);
        mxUtils.remove(connectingEdge, node.connectsAsTarget);
      } else if (reversedOverSwimlane) {
        connectingEdge.invert();
        mxUtils.remove(connectingEdge, parent.connectsAsTarget);
        node.connectsAsTarget.push(connectingEdge);
        parent.connectsAsSource.push(connectingEdge);
        mxUtils.remove(connectingEdge, node.connectsAsSource);
      }

      var cellId = mxCellPath.create(node.cell);
      seenNodes[cellId] = node;
      delete unseenNodes[cellId];
    },
    rootsArray,
    true,
    null
  );
};

/**
 * layout/hierarchical/mxHierarchicalLayout.js
 */
/**
 * Copyright (c) 2006-2018, JGraph Ltd
 * Copyright (c) 2006-2018, Gaudenz Alder
 */
/**
 * Class: mxHierarchicalLayout
 *
 * A hierarchical layout algorithm.
 *
 * Constructor: mxHierarchicalLayout
 *
 * Constructs a new hierarchical layout algorithm.
 *
 * Arguments:
 *
 * graph - Reference to the enclosing <mxGraph>.
 * orientation - Optional constant that defines the orientation of this
 * layout.
 * deterministic - Optional boolean that specifies if this layout should be
 * deterministic. Default is true.
 */
function mxHierarchicalLayout(graph, orientation, deterministic) {
  mxGraphLayout.call(this, graph);
  this.orientation = orientation != null ? orientation : mxConstants.DIRECTION_NORTH;
  this.deterministic = deterministic != null ? deterministic : true;
}

var mxHierarchicalEdgeStyle = {
  ORTHOGONAL: 1,
  POLYLINE: 2,
  STRAIGHT: 3,
  CURVE: 4,
};

/**
 * Extends mxGraphLayout.
 */
mxHierarchicalLayout.prototype = new mxGraphLayout();
mxHierarchicalLayout.prototype.constructor = mxHierarchicalLayout;

/**
 * Variable: roots
 *
 * Holds the array of <mxCell> that this layout contains.
 */
mxHierarchicalLayout.prototype.roots = null;

/**
 * Variable: resizeParent
 *
 * Specifies if the parent should be resized after the layout so that it
 * contains all the child cells. Default is false. See also <parentBorder>.
 */
mxHierarchicalLayout.prototype.resizeParent = false;

/**
 * Variable: maintainParentLocation
 *
 * Specifies if the parent location should be maintained, so that the
 * top, left corner stays the same before and after execution of
 * the layout. Default is false for backwards compatibility.
 */
mxHierarchicalLayout.prototype.maintainParentLocation = false;

/**
 * Variable: moveParent
 *
 * Specifies if the parent should be moved if <resizeParent> is enabled.
 * Default is false.
 */
mxHierarchicalLayout.prototype.moveParent = false;

/**
 * Variable: parentBorder
 *
 * The border to be added around the children if the parent is to be
 * resized using <resizeParent>. Default is 0.
 */
mxHierarchicalLayout.prototype.parentBorder = 0;

/**
 * Variable: intraCellSpacing
 *
 * The spacing buffer added between cells on the same layer. Default is 30.
 */
mxHierarchicalLayout.prototype.intraCellSpacing = 30;

/**
 * Variable: interRankCellSpacing
 *
 * The spacing buffer added between cell on adjacent layers. Default is 100.
 */
mxHierarchicalLayout.prototype.interRankCellSpacing = 100;

/**
 * Variable: interHierarchySpacing
 *
 * The spacing buffer between unconnected hierarchies. Default is 60.
 */
mxHierarchicalLayout.prototype.interHierarchySpacing = 60;

/**
 * Variable: parallelEdgeSpacing
 *
 * The distance between each parallel edge on each ranks for long edges.
 * Default is 10.
 */
mxHierarchicalLayout.prototype.parallelEdgeSpacing = 10;

/**
 * Variable: orientation
 *
 * The position of the root node(s) relative to the laid out graph in.
 * Default is <mxConstants.DIRECTION_NORTH>.
 */
mxHierarchicalLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;

/**
 * Variable: fineTuning
 *
 * Whether or not to perform local optimisations and iterate multiple times
 * through the algorithm. Default is true.
 */
mxHierarchicalLayout.prototype.fineTuning = true;

/**
 *
 * Variable: tightenToSource
 *
 * Whether or not to tighten the assigned ranks of vertices up towards
 * the source cells. Default is true.
 */
mxHierarchicalLayout.prototype.tightenToSource = true;

/**
 * Variable: disableEdgeStyle
 *
 * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
 * modified by the result. Default is true.
 */
mxHierarchicalLayout.prototype.disableEdgeStyle = true;

/**
 * Variable: traverseAncestors
 *
 * Whether or not to drill into child cells and layout in reverse
 * group order. This also cause the layout to navigate edges whose
 * terminal vertices have different parents but are in the same
 * ancestry chain. Default is true.
 */
mxHierarchicalLayout.prototype.traverseAncestors = true;

/**
 * Variable: model
 *
 * The internal <mxGraphHierarchyModel> formed of the layout.
 */
mxHierarchicalLayout.prototype.model = null;

/**
 * Variable: edgesSet
 *
 * A cache of edges whose source terminal is the key
 */
mxHierarchicalLayout.prototype.edgesCache = null;

/**
 * Variable: edgesSet
 *
 * A cache of edges whose source terminal is the key
 */
mxHierarchicalLayout.prototype.edgeSourceTermCache = null;

/**
 * Variable: edgesSet
 *
 * A cache of edges whose source terminal is the key
 */
mxHierarchicalLayout.prototype.edgesTargetTermCache = null;

/**
 * Variable: edgeStyle
 *
 * The style to apply between cell layers to edge segments.
 * Default is <mxHierarchicalEdgeStyle.POLYLINE>.
 */
mxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;

/**
 * Function: getModel
 *
 * Returns the internal <mxGraphHierarchyModel> for this layout algorithm.
 */
mxHierarchicalLayout.prototype.getModel = function () {
  return this.model;
};

/**
 * Function: execute
 *
 * Executes the layout for the children of the specified parent.
 *
 * Parameters:
 *
 * parent - Parent <mxCell> that contains the children to be laid out.
 * roots - Optional starting roots of the layout.
 */
mxHierarchicalLayout.prototype.execute = function (parent, roots) {
  this.parent = parent;
  var model = this.graph.model;
  this.edgesCache = new mxDictionary();
  this.edgeSourceTermCache = new mxDictionary();
  this.edgesTargetTermCache = new mxDictionary();

  if (roots != null && !(roots instanceof Array)) {
    roots = [roots];
  }

  // If the roots are set and the parent is set, only
  // use the roots that are some dependent of the that
  // parent.
  // If just the root are set, use them as-is
  // If just the parent is set use it's immediate
  // children as the initial set

  if (roots == null && parent == null) {
    // TODO indicate the problem
    return;
  }

  //  Maintaining parent location
  this.parentX = null;
  this.parentY = null;

  if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation) {
    var geo = this.graph.getCellGeometry(parent);

    if (geo != null) {
      this.parentX = geo.x;
      this.parentY = geo.y;
    }
  }

  if (roots != null) {
    var rootsCopy = [];

    for (var i = 0; i < roots.length; i++) {
      var ancestor = parent != null ? model.isAncestor(parent, roots[i]) : true;

      if (ancestor && model.isVertex(roots[i])) {
        rootsCopy.push(roots[i]);
      }
    }

    this.roots = rootsCopy;
  }

  model.beginUpdate();
  try {
    this.run(parent);

    if (this.resizeParent && !this.graph.isCellCollapsed(parent)) {
      this.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);
    }

    // Maintaining parent location
    if (this.parentX != null && this.parentY != null) {
      var geo = this.graph.getCellGeometry(parent);

      if (geo != null) {
        geo = geo.clone();
        geo.x = this.parentX;
        geo.y = this.parentY;
        model.setGeometry(parent, geo);
      }
    }
  } finally {
    model.endUpdate();
  }
};

/**
 * Function: findRoots
 *
 * Returns all visible children in the given parent which do not have
 * incoming edges. If the result is empty then the children with the
 * maximum difference between incoming and outgoing edges are returned.
 * This takes into account edges that are being promoted to the given
 * root due to invisible children or collapsed cells.
 *
 * Parameters:
 *
 * parent - <mxCell> whose children should be checked.
 * vertices - array of vertices to limit search to
 */
mxHierarchicalLayout.prototype.findRoots = function (parent, vertices) {
  var roots = [];

  if (parent != null && vertices != null) {
    var model = this.graph.model;
    var best = null;
    var maxDiff = -100000;

    for (var i in vertices) {
      var cell = vertices[i];

      if (model.isVertex(cell) && this.graph.isCellVisible(cell)) {
        var conns = this.getEdges(cell);
        var fanOut = 0;
        var fanIn = 0;

        for (var k = 0; k < conns.length; k++) {
          var src = this.getVisibleTerminal(conns[k], true);

          if (src == cell) {
            fanOut++;
          } else {
            fanIn++;
          }
        }

        if (fanIn == 0 && fanOut > 0) {
          roots.push(cell);
        }

        var diff = fanOut - fanIn;

        if (diff > maxDiff) {
          maxDiff = diff;
          best = cell;
        }
      }
    }

    if (roots.length == 0 && best != null) {
      roots.push(best);
    }
  }

  return roots;
};

/**
 * Function: getEdges
 *
 * Returns the connected edges for the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> whose edges should be returned.
 */
mxHierarchicalLayout.prototype.getEdges = function (cell) {
  var cachedEdges = this.edgesCache.get(cell);

  if (cachedEdges != null) {
    return cachedEdges;
  }

  var model = this.graph.model;
  var edges = [];
  var isCollapsed = this.graph.isCellCollapsed(cell);
  var childCount = model.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    var child = model.getChildAt(cell, i);

    if (this.isPort(child)) {
      edges = edges.concat(model.getEdges(child, true, true));
    } else if (isCollapsed || !this.graph.isCellVisible(child)) {
      edges = edges.concat(model.getEdges(child, true, true));
    }
  }

  edges = edges.concat(model.getEdges(cell, true, true));
  var result = [];

  for (var i = 0; i < edges.length; i++) {
    var source = this.getVisibleTerminal(edges[i], true);
    var target = this.getVisibleTerminal(edges[i], false);

    if (
      source == target ||
      (source != target &&
        ((target == cell && (this.parent == null || this.isAncestor(this.parent, source, this.traverseAncestors))) ||
          (source == cell && (this.parent == null || this.isAncestor(this.parent, target, this.traverseAncestors)))))
    ) {
      result.push(edges[i]);
    }
  }

  this.edgesCache.put(cell, result);

  return result;
};

/**
 * Function: getVisibleTerminal
 *
 * Helper function to return visible terminal for edge allowing for ports
 *
 * Parameters:
 *
 * edge - <mxCell> whose edges should be returned.
 * source - Boolean that specifies whether the source or target terminal is to be returned
 */
mxHierarchicalLayout.prototype.getVisibleTerminal = function (edge, source) {
  var terminalCache = this.edgesTargetTermCache;

  if (source) {
    terminalCache = this.edgeSourceTermCache;
  }

  var term = terminalCache.get(edge);

  if (term != null) {
    return term;
  }

  var state = this.graph.view.getState(edge);

  var terminal = state != null ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);

  if (terminal == null) {
    terminal = state != null ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
  }

  if (terminal != null) {
    if (this.isPort(terminal)) {
      terminal = this.graph.model.getParent(terminal);
    }

    terminalCache.put(edge, terminal);
  }

  return terminal;
};

/**
 * Function: run
 *
 * The API method used to exercise the layout upon the graph description
 * and produce a separate description of the vertex position and edge
 * routing changes made. It runs each stage of the layout that has been
 * created.
 */
mxHierarchicalLayout.prototype.run = function (parent) {
  // Separate out unconnected hierarchies
  var hierarchyVertices = [];
  var allVertexSet = [];

  if (this.roots == null && parent != null) {
    var filledVertexSet = Object();
    this.filterDescendants(parent, filledVertexSet);

    this.roots = [];
    var filledVertexSetEmpty = true;

    // Poor man's isSetEmpty
    for (var key in filledVertexSet) {
      if (filledVertexSet[key] != null) {
        filledVertexSetEmpty = false;
        break;
      }
    }

    while (!filledVertexSetEmpty) {
      var candidateRoots = this.findRoots(parent, filledVertexSet);

      // If the candidate root is an unconnected group cell, remove it from
      // the layout. We may need a custom set that holds such groups and forces
      // them to be processed for resizing and/or moving.

      for (var i = 0; i < candidateRoots.length; i++) {
        var vertexSet = Object();
        hierarchyVertices.push(vertexSet);

        this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet, hierarchyVertices, filledVertexSet);
      }

      for (var i = 0; i < candidateRoots.length; i++) {
        this.roots.push(candidateRoots[i]);
      }

      filledVertexSetEmpty = true;

      // Poor man's isSetEmpty
      for (var key in filledVertexSet) {
        if (filledVertexSet[key] != null) {
          filledVertexSetEmpty = false;
          break;
        }
      }
    }
  } else {
    // Find vertex set as directed traversal from roots

    for (var i = 0; i < this.roots.length; i++) {
      var vertexSet = Object();
      hierarchyVertices.push(vertexSet);

      this.traverse(this.roots[i], true, null, allVertexSet, vertexSet, hierarchyVertices, null);
    }
  }

  // Iterate through the result removing parents who have children in this layout

  // Perform a layout for each seperate hierarchy
  // Track initial coordinate x-positioning
  var initialX = 0;

  for (var i = 0; i < hierarchyVertices.length; i++) {
    var vertexSet = hierarchyVertices[i];
    var tmp = [];

    for (var key in vertexSet) {
      tmp.push(vertexSet[key]);
    }

    this.model = new mxGraphHierarchyModel(this, tmp, this.roots, parent, this.tightenToSource);

    this.cycleStage(parent);
    this.layeringStage();

    this.crossingStage(parent);
    initialX = this.placementStage(initialX, parent);
  }
};

/**
 * Function: filterDescendants
 *
 * Creates an array of descendant cells
 */
mxHierarchicalLayout.prototype.filterDescendants = function (cell, result) {
  var model = this.graph.model;

  if (model.isVertex(cell) && cell != this.parent && this.graph.isCellVisible(cell)) {
    result[mxObjectIdentity.get(cell)] = cell;
  }

  if (this.traverseAncestors || (cell == this.parent && this.graph.isCellVisible(cell))) {
    var childCount = model.getChildCount(cell);

    for (var i = 0; i < childCount; i++) {
      var child = model.getChildAt(cell, i);

      // Ignore ports in the layout vertex list, they are dealt with
      // in the traversal mechanisms
      if (!this.isPort(child)) {
        this.filterDescendants(child, result);
      }
    }
  }
};

/**
 * Function: isPort
 *
 * Returns true if the given cell is a "port", that is, when connecting to
 * it, its parent is the connecting vertex in terms of graph traversal
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the port.
 */
mxHierarchicalLayout.prototype.isPort = function (cell) {
  if (cell != null && cell.geometry != null) {
    return cell.geometry.relative;
  } else {
    return false;
  }
};

/**
 * Function: getEdgesBetween
 *
 * Returns the edges between the given source and target. This takes into
 * account collapsed and invisible cells and ports.
 *
 * Parameters:
 *
 * source -
 * target -
 * directed -
 */
mxHierarchicalLayout.prototype.getEdgesBetween = function (source, target, directed) {
  directed = directed != null ? directed : false;
  var edges = this.getEdges(source);
  var result = [];

  // Checks if the edge is connected to the correct
  // cell and returns the first match
  for (var i = 0; i < edges.length; i++) {
    var src = this.getVisibleTerminal(edges[i], true);
    var trg = this.getVisibleTerminal(edges[i], false);

    if ((src == source && trg == target) || (!directed && src == target && trg == source)) {
      result.push(edges[i]);
    }
  }

  return result;
};

/**
 * Traverses the (directed) graph invoking the given function for each
 * visited vertex and edge. The function is invoked with the current vertex
 * and the incoming edge as a parameter. This implementation makes sure
 * each vertex is only visited once. The function may return false if the
 * traversal should stop at the given vertex.
 *
 * Parameters:
 *
 * vertex - <mxCell> that represents the vertex where the traversal starts.
 * directed - boolean indicating if edges should only be traversed
 * from source to target. Default is true.
 * edge - Optional <mxCell> that represents the incoming edge. This is
 * null for the first step of the traversal.
 * allVertices - Array of cell paths for the visited cells.
 */
mxHierarchicalLayout.prototype.traverse = function (
  vertex,
  directed,
  edge,
  allVertices,
  currentComp,
  hierarchyVertices,
  filledVertexSet
) {
  if (vertex != null && allVertices != null) {
    // Has this vertex been seen before in any traversal
    // And if the filled vertex set is populated, only
    // process vertices in that it contains
    var vertexID = mxObjectIdentity.get(vertex);

    if (allVertices[vertexID] == null && (filledVertexSet == null ? true : filledVertexSet[vertexID] != null)) {
      if (currentComp[vertexID] == null) {
        currentComp[vertexID] = vertex;
      }
      if (allVertices[vertexID] == null) {
        allVertices[vertexID] = vertex;
      }

      if (filledVertexSet !== null) {
        delete filledVertexSet[vertexID];
      }

      var edges = this.getEdges(vertex);
      var edgeIsSource = [];

      for (var i = 0; i < edges.length; i++) {
        edgeIsSource[i] = this.getVisibleTerminal(edges[i], true) == vertex;
      }

      for (var i = 0; i < edges.length; i++) {
        if (!directed || edgeIsSource[i]) {
          var next = this.getVisibleTerminal(edges[i], !edgeIsSource[i]);

          // Check whether there are more edges incoming from the target vertex than outgoing
          // The hierarchical model treats bi-directional parallel edges as being sourced
          // from the more "sourced" terminal. If the directions are equal in number, the direction
          // is that of the natural direction from the roots of the layout.
          // The checks below are slightly more verbose than need be for performance reasons
          var netCount = 1;

          for (var j = 0; j < edges.length; j++) {
            if (j == i) {
              continue;
            } else {
              var isSource2 = edgeIsSource[j];
              var otherTerm = this.getVisibleTerminal(edges[j], !isSource2);

              if (otherTerm == next) {
                if (isSource2) {
                  netCount++;
                } else {
                  netCount--;
                }
              }
            }
          }

          if (netCount >= 0) {
            currentComp = this.traverse(
              next,
              directed,
              edges[i],
              allVertices,
              currentComp,
              hierarchyVertices,
              filledVertexSet
            );
          }
        }
      }
    } else {
      if (currentComp[vertexID] == null) {
        // We've seen this vertex before, but not in the current component
        // This component and the one it's in need to be merged

        for (var i = 0; i < hierarchyVertices.length; i++) {
          var comp = hierarchyVertices[i];

          if (comp[vertexID] != null) {
            for (var key in comp) {
              currentComp[key] = comp[key];
            }

            // Remove the current component from the hierarchy set
            hierarchyVertices.splice(i, 1);
            return currentComp;
          }
        }
      }
    }
  }

  return currentComp;
};

/**
 * Function: cycleStage
 *
 * Executes the cycle stage using mxMinimumCycleRemover.
 */
mxHierarchicalLayout.prototype.cycleStage = function (parent) {
  var cycleStage = new mxMinimumCycleRemover(this);
  cycleStage.execute(parent);
};

/**
 * Function: layeringStage
 *
 * Implements first stage of a Sugiyama layout.
 */
mxHierarchicalLayout.prototype.layeringStage = function () {
  this.model.initialRank();
  this.model.fixRanks();
};

/**
 * Function: crossingStage
 *
 * Executes the crossing stage using mxMedianHybridCrossingReduction.
 */
mxHierarchicalLayout.prototype.crossingStage = function (parent) {
  var crossingStage = new mxMedianHybridCrossingReduction(this);
  crossingStage.execute(parent);
};

/**
 * Function: placementStage
 *
 * Executes the placement stage using mxCoordinateAssignment.
 */
mxHierarchicalLayout.prototype.placementStage = function (initialX, parent) {
  var placementStage = new mxCoordinateAssignment(
    this,
    this.intraCellSpacing,
    this.interRankCellSpacing,
    this.orientation,
    initialX,
    this.parallelEdgeSpacing
  );
  placementStage.fineTuning = this.fineTuning;
  placementStage.execute(parent);

  return placementStage.limitX + this.interHierarchySpacing;
};

/**
 * layout/hierarchical/mxSwimlaneLayout.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxSwimlaneLayout
 *
 * A hierarchical layout algorithm.
 *
 * Constructor: mxSwimlaneLayout
 *
 * Constructs a new hierarchical layout algorithm.
 *
 * Arguments:
 *
 * graph - Reference to the enclosing <mxGraph>.
 * orientation - Optional constant that defines the orientation of this
 * layout.
 * deterministic - Optional boolean that specifies if this layout should be
 * deterministic. Default is true.
 */
function mxSwimlaneLayout(graph, orientation, deterministic) {
  mxGraphLayout.call(this, graph);
  this.orientation = orientation != null ? orientation : mxConstants.DIRECTION_NORTH;
  this.deterministic = deterministic != null ? deterministic : true;
}

/**
 * Extends mxGraphLayout.
 */
mxSwimlaneLayout.prototype = new mxGraphLayout();
mxSwimlaneLayout.prototype.constructor = mxSwimlaneLayout;

/**
 * Variable: roots
 *
 * Holds the array of <mxCell> that this layout contains.
 */
mxSwimlaneLayout.prototype.roots = null;

/**
 * Variable: swimlanes
 *
 * Holds the array of <mxCell> of the ordered swimlanes to lay out
 */
mxSwimlaneLayout.prototype.swimlanes = null;

/**
 * Variable: dummyVertexWidth
 *
 * The cell width of any dummy vertices inserted
 */
mxSwimlaneLayout.prototype.dummyVertexWidth = 50;

/**
 * Variable: resizeParent
 *
 * Specifies if the parent should be resized after the layout so that it
 * contains all the child cells. Default is false. See also <parentBorder>.
 */
mxSwimlaneLayout.prototype.resizeParent = false;

/**
 * Variable: maintainParentLocation
 *
 * Specifies if the parent location should be maintained, so that the
 * top, left corner stays the same before and after execution of
 * the layout. Default is false for backwards compatibility.
 */
mxSwimlaneLayout.prototype.maintainParentLocation = false;

/**
 * Variable: moveParent
 *
 * Specifies if the parent should be moved if <resizeParent> is enabled.
 * Default is false.
 */
mxSwimlaneLayout.prototype.moveParent = false;

/**
 * Variable: parentBorder
 *
 * The border to be added around the children if the parent is to be
 * resized using <resizeParent>. Default is 30.
 */
mxSwimlaneLayout.prototype.parentBorder = 30;

/**
 * Variable: intraCellSpacing
 *
 * The spacing buffer added between cells on the same layer. Default is 30.
 */
mxSwimlaneLayout.prototype.intraCellSpacing = 30;

/**
 * Variable: interRankCellSpacing
 *
 * The spacing buffer added between cell on adjacent layers. Default is 100.
 */
mxSwimlaneLayout.prototype.interRankCellSpacing = 100;

/**
 * Variable: interHierarchySpacing
 *
 * The spacing buffer between unconnected hierarchies. Default is 60.
 */
mxSwimlaneLayout.prototype.interHierarchySpacing = 60;

/**
 * Variable: parallelEdgeSpacing
 *
 * The distance between each parallel edge on each ranks for long edges.
 * Default is 10.
 */
mxSwimlaneLayout.prototype.parallelEdgeSpacing = 10;

/**
 * Variable: orientation
 *
 * The position of the root node(s) relative to the laid out graph in.
 * Default is <mxConstants.DIRECTION_NORTH>.
 */
mxSwimlaneLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;

/**
 * Variable: fineTuning
 *
 * Whether or not to perform local optimisations and iterate multiple times
 * through the algorithm. Default is true.
 */
mxSwimlaneLayout.prototype.fineTuning = true;

/**
 * Variable: tightenToSource
 *
 * Whether or not to tighten the assigned ranks of vertices up towards
 * the source cells. Default is true.
 */
mxSwimlaneLayout.prototype.tightenToSource = true;

/**
 * Variable: disableEdgeStyle
 *
 * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
 * modified by the result. Default is true.
 */
mxSwimlaneLayout.prototype.disableEdgeStyle = true;

/**
 * Variable: traverseAncestors
 *
 * Whether or not to drill into child cells and layout in reverse
 * group order. This also cause the layout to navigate edges whose
 * terminal vertices have different parents but are in the same
 * ancestry chain. Default is true.
 */
mxSwimlaneLayout.prototype.traverseAncestors = true;

/**
 * Variable: model
 *
 * The internal <mxSwimlaneModel> formed of the layout.
 */
mxSwimlaneLayout.prototype.model = null;

/**
 * Variable: edgesSet
 *
 * A cache of edges whose source terminal is the key
 */
mxSwimlaneLayout.prototype.edgesCache = null;

/**
 * Variable: edgesSet
 *
 * A cache of edges whose source terminal is the key
 */
mxHierarchicalLayout.prototype.edgeSourceTermCache = null;

/**
 * Variable: edgesSet
 *
 * A cache of edges whose source terminal is the key
 */
mxHierarchicalLayout.prototype.edgesTargetTermCache = null;

/**
 * Variable: edgeStyle
 *
 * The style to apply between cell layers to edge segments.
 * Default is <mxHierarchicalEdgeStyle.POLYLINE>.
 */
mxHierarchicalLayout.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;

/**
 * Function: getModel
 *
 * Returns the internal <mxSwimlaneModel> for this layout algorithm.
 */
mxSwimlaneLayout.prototype.getModel = function () {
  return this.model;
};

/**
 * Function: execute
 *
 * Executes the layout for the children of the specified parent.
 *
 * Parameters:
 *
 * parent - Parent <mxCell> that contains the children to be laid out.
 * swimlanes - Ordered array of swimlanes to be laid out
 */
mxSwimlaneLayout.prototype.execute = function (parent, swimlanes) {
  this.parent = parent;
  var model = this.graph.model;
  this.edgesCache = new mxDictionary();
  this.edgeSourceTermCache = new mxDictionary();
  this.edgesTargetTermCache = new mxDictionary();

  // If the roots are set and the parent is set, only
  // use the roots that are some dependent of the that
  // parent.
  // If just the root are set, use them as-is
  // If just the parent is set use it's immediate
  // children as the initial set

  if (swimlanes == null || swimlanes.length < 1) {
    // TODO indicate the problem
    return;
  }

  if (parent == null) {
    parent = model.getParent(swimlanes[0]);
  }

  //  Maintaining parent location
  this.parentX = null;
  this.parentY = null;

  if (parent != this.root && model.isVertex(parent) != null && this.maintainParentLocation) {
    var geo = this.graph.getCellGeometry(parent);

    if (geo != null) {
      this.parentX = geo.x;
      this.parentY = geo.y;
    }
  }

  this.swimlanes = swimlanes;
  var dummyVertices = [];
  // Check the swimlanes all have vertices
  // in them
  for (var i = 0; i < swimlanes.length; i++) {
    var children = this.graph.getChildCells(swimlanes[i]);

    if (children == null || children.length == 0) {
      var vertex = this.graph.insertVertex(swimlanes[i], null, null, 0, 0, this.dummyVertexWidth, 0);
      dummyVertices.push(vertex);
    }
  }

  model.beginUpdate();
  try {
    this.run(parent);

    if (this.resizeParent && !this.graph.isCellCollapsed(parent)) {
      this.graph.updateGroupBounds([parent], this.parentBorder, this.moveParent);
    }

    // Maintaining parent location
    if (this.parentX != null && this.parentY != null) {
      var geo = this.graph.getCellGeometry(parent);

      if (geo != null) {
        geo = geo.clone();
        geo.x = this.parentX;
        geo.y = this.parentY;
        model.setGeometry(parent, geo);
      }
    }

    this.graph.removeCells(dummyVertices);
  } finally {
    model.endUpdate();
  }
};

/**
 * Function: updateGroupBounds
 *
 * Updates the bounds of the given array of groups so that it includes
 * all child vertices.
 *
 */
mxSwimlaneLayout.prototype.updateGroupBounds = function () {
  // Get all vertices and edge in the layout
  var cells = [];
  var model = this.model;

  for (var key in model.edgeMapper) {
    var edge = model.edgeMapper[key];

    for (var i = 0; i < edge.edges.length; i++) {
      cells.push(edge.edges[i]);
    }
  }

  var layoutBounds = this.graph.getBoundingBoxFromGeometry(cells, true);
  var childBounds = [];

  for (var i = 0; i < this.swimlanes.length; i++) {
    var lane = this.swimlanes[i];
    var geo = this.graph.getCellGeometry(lane);

    if (geo != null) {
      var children = this.graph.getChildCells(lane);

      var size = this.graph.isSwimlane(lane) ? this.graph.getStartSize(lane) : new mxRectangle();

      var bounds = this.graph.getBoundingBoxFromGeometry(children);
      childBounds[i] = bounds;
      var childrenY = bounds.y + geo.y - size.height - this.parentBorder;
      var maxChildrenY = bounds.y + geo.y + bounds.height;

      if (layoutBounds == null) {
        layoutBounds = new mxRectangle(0, childrenY, 0, maxChildrenY - childrenY);
      } else {
        layoutBounds.y = Math.min(layoutBounds.y, childrenY);
        var maxY = Math.max(layoutBounds.y + layoutBounds.height, maxChildrenY);
        layoutBounds.height = maxY - layoutBounds.y;
      }
    }
  }

  for (var i = 0; i < this.swimlanes.length; i++) {
    var lane = this.swimlanes[i];
    var geo = this.graph.getCellGeometry(lane);

    if (geo != null) {
      var children = this.graph.getChildCells(lane);

      var size = this.graph.isSwimlane(lane) ? this.graph.getStartSize(lane) : new mxRectangle();

      var newGeo = geo.clone();

      var leftGroupBorder = i == 0 ? this.parentBorder : this.interRankCellSpacing / 2;
      var w = size.width + leftGroupBorder;
      var x = childBounds[i].x - w;
      var y = layoutBounds.y - this.parentBorder;

      newGeo.x += x;
      newGeo.y = y;

      newGeo.width = childBounds[i].width + w + this.interRankCellSpacing / 2;
      newGeo.height = layoutBounds.height + size.height + 2 * this.parentBorder;

      this.graph.model.setGeometry(lane, newGeo);
      this.graph.moveCells(children, -x, geo.y - y);
    }
  }
};

/**
 * Function: findRoots
 *
 * Returns all visible children in the given parent which do not have
 * incoming edges. If the result is empty then the children with the
 * maximum difference between incoming and outgoing edges are returned.
 * This takes into account edges that are being promoted to the given
 * root due to invisible children or collapsed cells.
 *
 * Parameters:
 *
 * parent - <mxCell> whose children should be checked.
 * vertices - array of vertices to limit search to
 */
mxSwimlaneLayout.prototype.findRoots = function (parent, vertices) {
  var roots = [];

  if (parent != null && vertices != null) {
    var model = this.graph.model;
    var best = null;
    var maxDiff = -100000;

    for (var i in vertices) {
      var cell = vertices[i];

      if (cell != null && model.isVertex(cell) && this.graph.isCellVisible(cell) && model.isAncestor(parent, cell)) {
        var conns = this.getEdges(cell);
        var fanOut = 0;
        var fanIn = 0;

        for (var k = 0; k < conns.length; k++) {
          var src = this.getVisibleTerminal(conns[k], true);

          if (src == cell) {
            // Only count connection within this swimlane
            var other = this.getVisibleTerminal(conns[k], false);

            if (model.isAncestor(parent, other)) {
              fanOut++;
            }
          } else if (model.isAncestor(parent, src)) {
            fanIn++;
          }
        }

        if (fanIn == 0 && fanOut > 0) {
          roots.push(cell);
        }

        var diff = fanOut - fanIn;

        if (diff > maxDiff) {
          maxDiff = diff;
          best = cell;
        }
      }
    }

    if (roots.length == 0 && best != null) {
      roots.push(best);
    }
  }

  return roots;
};

/**
 * Function: getEdges
 *
 * Returns the connected edges for the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> whose edges should be returned.
 */
mxSwimlaneLayout.prototype.getEdges = function (cell) {
  var cachedEdges = this.edgesCache.get(cell);

  if (cachedEdges != null) {
    return cachedEdges;
  }

  var model = this.graph.model;
  var edges = [];
  var isCollapsed = this.graph.isCellCollapsed(cell);
  var childCount = model.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    var child = model.getChildAt(cell, i);

    if (this.isPort(child)) {
      edges = edges.concat(model.getEdges(child, true, true));
    } else if (isCollapsed || !this.graph.isCellVisible(child)) {
      edges = edges.concat(model.getEdges(child, true, true));
    }
  }

  edges = edges.concat(model.getEdges(cell, true, true));
  var result = [];

  for (var i = 0; i < edges.length; i++) {
    var source = this.getVisibleTerminal(edges[i], true);
    var target = this.getVisibleTerminal(edges[i], false);

    if (
      source == target ||
      (source != target &&
        ((target == cell &&
          (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
          (source == cell &&
            (this.parent == null || this.graph.isValidAncestor(target, this.parent, this.traverseAncestors)))))
    ) {
      result.push(edges[i]);
    }
  }

  this.edgesCache.put(cell, result);

  return result;
};

/**
 * Function: getVisibleTerminal
 *
 * Helper function to return visible terminal for edge allowing for ports
 *
 * Parameters:
 *
 * edge - <mxCell> whose edges should be returned.
 * source - Boolean that specifies whether the source or target terminal is to be returned
 */
mxSwimlaneLayout.prototype.getVisibleTerminal = function (edge, source) {
  var terminalCache = this.edgesTargetTermCache;

  if (source) {
    terminalCache = this.edgeSourceTermCache;
  }

  var term = terminalCache.get(edge);

  if (term != null) {
    return term;
  }

  var state = this.graph.view.getState(edge);

  var terminal = state != null ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);

  if (terminal == null) {
    terminal = state != null ? state.getVisibleTerminal(source) : this.graph.view.getVisibleTerminal(edge, source);
  }

  if (terminal != null) {
    if (this.isPort(terminal)) {
      terminal = this.graph.model.getParent(terminal);
    }

    terminalCache.put(edge, terminal);
  }

  return terminal;
};

/**
 * Function: run
 *
 * The API method used to exercise the layout upon the graph description
 * and produce a separate description of the vertex position and edge
 * routing changes made. It runs each stage of the layout that has been
 * created.
 */
mxSwimlaneLayout.prototype.run = function (parent) {
  // Separate out unconnected hierarchies
  var hierarchyVertices = [];
  var allVertexSet = Object();

  if (this.swimlanes != null && this.swimlanes.length > 0 && parent != null) {
    var filledVertexSet = Object();

    for (var i = 0; i < this.swimlanes.length; i++) {
      this.filterDescendants(this.swimlanes[i], filledVertexSet);
    }

    this.roots = [];
    var filledVertexSetEmpty = true;

    // Poor man's isSetEmpty
    for (var key in filledVertexSet) {
      if (filledVertexSet[key] != null) {
        filledVertexSetEmpty = false;
        break;
      }
    }

    // Only test for candidates in each swimlane in order
    var laneCounter = 0;

    while (!filledVertexSetEmpty && laneCounter < this.swimlanes.length) {
      var candidateRoots = this.findRoots(this.swimlanes[laneCounter], filledVertexSet);

      if (candidateRoots.length == 0) {
        laneCounter++;
        continue;
      }

      // If the candidate root is an unconnected group cell, remove it from
      // the layout. We may need a custom set that holds such groups and forces
      // them to be processed for resizing and/or moving.
      for (var i = 0; i < candidateRoots.length; i++) {
        var vertexSet = Object();
        hierarchyVertices.push(vertexSet);

        this.traverse(
          candidateRoots[i],
          true,
          null,
          allVertexSet,
          vertexSet,
          hierarchyVertices,
          filledVertexSet,
          laneCounter
        );
      }

      for (var i = 0; i < candidateRoots.length; i++) {
        this.roots.push(candidateRoots[i]);
      }

      filledVertexSetEmpty = true;

      // Poor man's isSetEmpty
      for (var key in filledVertexSet) {
        if (filledVertexSet[key] != null) {
          filledVertexSetEmpty = false;
          break;
        }
      }
    }
  } else {
    // Find vertex set as directed traversal from roots

    for (var i = 0; i < this.roots.length; i++) {
      var vertexSet = Object();
      hierarchyVertices.push(vertexSet);

      this.traverse(this.roots[i], true, null, allVertexSet, vertexSet, hierarchyVertices, null);
    }
  }

  var tmp = [];

  for (var key in allVertexSet) {
    tmp.push(allVertexSet[key]);
  }

  this.model = new mxSwimlaneModel(this, tmp, this.roots, parent, this.tightenToSource);

  this.cycleStage(parent);
  this.layeringStage();

  this.crossingStage(parent);
  this.placementStage(0, parent);
};

/**
 * Function: filterDescendants
 *
 * Creates an array of descendant cells
 */
mxSwimlaneLayout.prototype.filterDescendants = function (cell, result) {
  var model = this.graph.model;

  if (
    model.isVertex(cell) &&
    cell != this.parent &&
    model.getParent(cell) != this.parent &&
    this.graph.isCellVisible(cell)
  ) {
    result[mxObjectIdentity.get(cell)] = cell;
  }

  if (this.traverseAncestors || (cell == this.parent && this.graph.isCellVisible(cell))) {
    var childCount = model.getChildCount(cell);

    for (var i = 0; i < childCount; i++) {
      var child = model.getChildAt(cell, i);

      // Ignore ports in the layout vertex list, they are dealt with
      // in the traversal mechanisms
      if (!this.isPort(child)) {
        this.filterDescendants(child, result);
      }
    }
  }
};

/**
 * Function: isPort
 *
 * Returns true if the given cell is a "port", that is, when connecting to
 * it, its parent is the connecting vertex in terms of graph traversal
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the port.
 */
mxSwimlaneLayout.prototype.isPort = function (cell) {
  if (cell.geometry.relative) {
    return true;
  }

  return false;
};

/**
 * Function: getEdgesBetween
 *
 * Returns the edges between the given source and target. This takes into
 * account collapsed and invisible cells and ports.
 *
 * Parameters:
 *
 * source -
 * target -
 * directed -
 */
mxSwimlaneLayout.prototype.getEdgesBetween = function (source, target, directed) {
  directed = directed != null ? directed : false;
  var edges = this.getEdges(source);
  var result = [];

  // Checks if the edge is connected to the correct
  // cell and returns the first match
  for (var i = 0; i < edges.length; i++) {
    var src = this.getVisibleTerminal(edges[i], true);
    var trg = this.getVisibleTerminal(edges[i], false);

    if ((src == source && trg == target) || (!directed && src == target && trg == source)) {
      result.push(edges[i]);
    }
  }

  return result;
};

/**
 * Traverses the (directed) graph invoking the given function for each
 * visited vertex and edge. The function is invoked with the current vertex
 * and the incoming edge as a parameter. This implementation makes sure
 * each vertex is only visited once. The function may return false if the
 * traversal should stop at the given vertex.
 *
 * Parameters:
 *
 * vertex - <mxCell> that represents the vertex where the traversal starts.
 * directed - boolean indicating if edges should only be traversed
 * from source to target. Default is true.
 * edge - Optional <mxCell> that represents the incoming edge. This is
 * null for the first step of the traversal.
 * allVertices - Array of cell paths for the visited cells.
 * swimlaneIndex - the laid out order index of the swimlane vertex is contained in
 */
mxSwimlaneLayout.prototype.traverse = function (
  vertex,
  directed,
  edge,
  allVertices,
  currentComp,
  hierarchyVertices,
  filledVertexSet,
  swimlaneIndex
) {
  if (vertex != null && allVertices != null) {
    // Has this vertex been seen before in any traversal
    // And if the filled vertex set is populated, only
    // process vertices in that it contains
    var vertexID = mxObjectIdentity.get(vertex);

    if (allVertices[vertexID] == null && (filledVertexSet == null ? true : filledVertexSet[vertexID] != null)) {
      if (currentComp[vertexID] == null) {
        currentComp[vertexID] = vertex;
      }
      if (allVertices[vertexID] == null) {
        allVertices[vertexID] = vertex;
      }

      if (filledVertexSet !== null) {
        delete filledVertexSet[vertexID];
      }

      var edges = this.getEdges(vertex);
      var model = this.graph.model;

      for (var i = 0; i < edges.length; i++) {
        var otherVertex = this.getVisibleTerminal(edges[i], true);
        var isSource = otherVertex == vertex;

        if (isSource) {
          otherVertex = this.getVisibleTerminal(edges[i], false);
        }

        var otherIndex = 0;
        // Get the swimlane index of the other terminal
        for (otherIndex = 0; otherIndex < this.swimlanes.length; otherIndex++) {
          if (model.isAncestor(this.swimlanes[otherIndex], otherVertex)) {
            break;
          }
        }

        if (otherIndex >= this.swimlanes.length) {
          continue;
        }

        // Traverse if the other vertex is within the same swimlane as
        // as the current vertex, or if the swimlane index of the other
        // vertex is greater than that of this vertex
        if (otherIndex > swimlaneIndex || ((!directed || isSource) && otherIndex == swimlaneIndex)) {
          currentComp = this.traverse(
            otherVertex,
            directed,
            edges[i],
            allVertices,
            currentComp,
            hierarchyVertices,
            filledVertexSet,
            otherIndex
          );
        }
      }
    } else {
      if (currentComp[vertexID] == null) {
        // We've seen this vertex before, but not in the current component
        // This component and the one it's in need to be merged
        for (var i = 0; i < hierarchyVertices.length; i++) {
          var comp = hierarchyVertices[i];

          if (comp[vertexID] != null) {
            for (var key in comp) {
              currentComp[key] = comp[key];
            }

            // Remove the current component from the hierarchy set
            hierarchyVertices.splice(i, 1);
            return currentComp;
          }
        }
      }
    }
  }

  return currentComp;
};

/**
 * Function: cycleStage
 *
 * Executes the cycle stage using mxMinimumCycleRemover.
 */
mxSwimlaneLayout.prototype.cycleStage = function (parent) {
  var cycleStage = new mxSwimlaneOrdering(this);
  cycleStage.execute(parent);
};

/**
 * Function: layeringStage
 *
 * Implements first stage of a Sugiyama layout.
 */
mxSwimlaneLayout.prototype.layeringStage = function () {
  this.model.initialRank();
  this.model.fixRanks();
};

/**
 * Function: crossingStage
 *
 * Executes the crossing stage using mxMedianHybridCrossingReduction.
 */
mxSwimlaneLayout.prototype.crossingStage = function (parent) {
  var crossingStage = new mxMedianHybridCrossingReduction(this);
  crossingStage.execute(parent);
};

/**
 * Function: placementStage
 *
 * Executes the placement stage using mxCoordinateAssignment.
 */
mxSwimlaneLayout.prototype.placementStage = function (initialX, parent) {
  var placementStage = new mxCoordinateAssignment(
    this,
    this.intraCellSpacing,
    this.interRankCellSpacing,
    this.orientation,
    initialX,
    this.parallelEdgeSpacing
  );
  placementStage.fineTuning = this.fineTuning;
  placementStage.execute(parent);

  return placementStage.limitX + this.interHierarchySpacing;
};

/**
 * model/mxGraphModel.js
 */
/**
 * Copyright (c) 2006-2018, JGraph Ltd
 * Copyright (c) 2006-2018, Gaudenz Alder
 */
/**
 * Class: mxGraphModel
 *
 * Extends <mxEventSource> to implement a graph model. The graph model acts as
 * a wrapper around the cells which are in charge of storing the actual graph
 * datastructure. The model acts as a transactional wrapper with event
 * notification for all changes, whereas the cells contain the atomic
 * operations for updating the actual datastructure.
 *
 * Layers:
 *
 * The cell hierarchy in the model must have a top-level root cell which
 * contains the layers (typically one default layer), which in turn contain the
 * top-level cells of the layers. This means each cell is contained in a layer.
 * If no layers are required, then all new cells should be added to the default
 * layer.
 *
 * Layers are useful for hiding and showing groups of cells, or for placing
 * groups of cells on top of other cells in the display. To identify a layer,
 * the <isLayer> function is used. It returns true if the parent of the given
 * cell is the root of the model.
 *
 * Events:
 *
 * See events section for more details. There is a new set of events for
 * tracking transactional changes as they happen. The events are called
 * startEdit for the initial beginUpdate, executed for each executed change
 * and endEdit for the terminal endUpdate. The executed event contains a
 * property called change which represents the change after execution.
 *
 * Encoding the model:
 *
 * To encode a graph model, use the following code:
 *
 * (code)
 * var enc = new mxCodec();
 * var node = enc.encode(graph.getModel());
 * (end)
 *
 * This will create an XML node that contains all the model information.
 *
 * Encoding and decoding changes:
 *
 * For the encoding of changes, a graph model listener is required that encodes
 * each change from the given array of changes.
 *
 * (code)
 * model.addListener(mxEvent.CHANGE, function(sender, evt)
 * {
 *   var changes = evt.getProperty('edit').changes;
 *   var nodes = [];
 *   var codec = new mxCodec();
 *
 *   for (var i = 0; i < changes.length; i++)
 *   {
 *     nodes.push(codec.encode(changes[i]));
 *   }
 *   // do something with the nodes
 * });
 * (end)
 *
 * For the decoding and execution of changes, the codec needs a lookup function
 * that allows it to resolve cell IDs as follows:
 *
 * (code)
 * var codec = new mxCodec();
 * codec.lookup = function(id)
 * {
 *   return model.getCell(id);
 * }
 * (end)
 *
 * For each encoded change (represented by a node), the following code can be
 * used to carry out the decoding and create a change object.
 *
 * (code)
 * var changes = [];
 * var change = codec.decode(node);
 * change.model = model;
 * change.execute();
 * changes.push(change);
 * (end)
 *
 * The changes can then be dispatched using the model as follows.
 *
 * (code)
 * var edit = new mxUndoableEdit(model, false);
 * edit.changes = changes;
 *
 * edit.notify = function()
 * {
 *   edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
 *   	'edit', edit, 'changes', edit.changes));
 *   edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
 *   	'edit', edit, 'changes', edit.changes));
 * }
 *
 * model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
 * model.fireEvent(new mxEventObject(mxEvent.CHANGE,
 * 		'edit', edit, 'changes', changes));
 * (end)
 *
 * Event: mxEvent.CHANGE
 *
 * Fires when an undoable edit is dispatched. The <code>edit</code> property
 * contains the <mxUndoableEdit>. The <code>changes</code> property contains
 * the array of atomic changes inside the undoable edit. The changes property
 * is <strong>deprecated</strong>, please use edit.changes instead.
 *
 * Example:
 *
 * For finding newly inserted cells, the following code can be used:
 *
 * (code)
 * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
 * {
 *   var changes = evt.getProperty('edit').changes;
 *
 *   for (var i = 0; i < changes.length; i++)
 *   {
 *     var change = changes[i];
 *
 *     if (change instanceof mxChildChange &&
 *       change.change.previous == null)
 *     {
 *       graph.startEditingAtCell(change.child);
 *       break;
 *     }
 *   }
 * });
 * (end)
 *
 *
 * Event: mxEvent.NOTIFY
 *
 * Same as <mxEvent.CHANGE>, this event can be used for classes that need to
 * implement a sync mechanism between this model and, say, a remote model. In
 * such a setup, only local changes should trigger a notify event and all
 * changes should trigger a change event.
 *
 * Event: mxEvent.EXECUTE
 *
 * Fires between begin- and endUpdate and after an atomic change was executed
 * in the model. The <code>change</code> property contains the atomic change
 * that was executed.
 *
 * Event: mxEvent.EXECUTED
 *
 * Fires between START_EDIT and END_EDIT after an atomic change was executed.
 * The <code>change</code> property contains the change that was executed.
 *
 * Event: mxEvent.BEGIN_UPDATE
 *
 * Fires after the <updateLevel> was incremented in <beginUpdate>. This event
 * contains no properties.
 *
 * Event: mxEvent.START_EDIT
 *
 * Fires after the <updateLevel> was changed from 0 to 1. This event
 * contains no properties.
 *
 * Event: mxEvent.END_UPDATE
 *
 * Fires after the <updateLevel> was decreased in <endUpdate> but before any
 * notification or change dispatching. The <code>edit</code> property contains
 * the <currentEdit>.
 *
 * Event: mxEvent.END_EDIT
 *
 * Fires after the <updateLevel> was changed from 1 to 0. This event
 * contains no properties.
 *
 * Event: mxEvent.BEFORE_UNDO
 *
 * Fires before the change is dispatched after the update level has reached 0
 * in <endUpdate>. The <code>edit</code> property contains the <curreneEdit>.
 *
 * Event: mxEvent.UNDO
 *
 * Fires after the change was dispatched in <endUpdate>. The <code>edit</code>
 * property contains the <currentEdit>.
 *
 * Constructor: mxGraphModel
 *
 * Constructs a new graph model. If no root is specified then a new root
 * <mxCell> with a default layer is created.
 *
 * Parameters:
 *
 * root - <mxCell> that represents the root cell.
 */
function mxGraphModel(root) {
  this.currentEdit = this.createUndoableEdit();

  if (root != null) {
    this.setRoot(root);
  } else {
    this.clear();
  }
}

/**
 * Extends mxEventSource.
 */
mxGraphModel.prototype = new mxEventSource();
mxGraphModel.prototype.constructor = mxGraphModel;

/**
 * Variable: root
 *
 * Holds the root cell, which in turn contains the cells that represent the
 * layers of the diagram as child cells. That is, the actual elements of the
 * diagram are supposed to live in the third generation of cells and below.
 */
mxGraphModel.prototype.root = null;

/**
 * Variable: cells
 *
 * Maps from Ids to cells.
 */
mxGraphModel.prototype.cells = null;

/**
 * Variable: maintainEdgeParent
 *
 * Specifies if edges should automatically be moved into the nearest common
 * ancestor of their terminals. Default is true.
 */
mxGraphModel.prototype.maintainEdgeParent = true;

/**
 * Variable: ignoreRelativeEdgeParent
 *
 * Specifies if relative edge parents should be ignored for finding the nearest
 * common ancestors of an edge's terminals. Default is true.
 */
mxGraphModel.prototype.ignoreRelativeEdgeParent = true;

/**
 * Variable: createIds
 *
 * Specifies if the model should automatically create Ids for new cells.
 * Default is true.
 */
mxGraphModel.prototype.createIds = true;

/**
 * Variable: prefix
 *
 * Defines the prefix of new Ids. Default is an empty string.
 */
mxGraphModel.prototype.prefix = '';

/**
 * Variable: postfix
 *
 * Defines the postfix of new Ids. Default is an empty string.
 */
mxGraphModel.prototype.postfix = '';

/**
 * Variable: nextId
 *
 * Specifies the next Id to be created. Initial value is 0.
 */
mxGraphModel.prototype.nextId = 0;

/**
 * Variable: currentEdit
 *
 * Holds the changes for the current transaction. If the transaction is
 * closed then a new object is created for this variable using
 * <createUndoableEdit>.
 */
mxGraphModel.prototype.currentEdit = null;

/**
 * Variable: updateLevel
 *
 * Counter for the depth of nested transactions. Each call to <beginUpdate>
 * will increment this number and each call to <endUpdate> will decrement
 * it. When the counter reaches 0, the transaction is closed and the
 * respective events are fired. Initial value is 0.
 */
mxGraphModel.prototype.updateLevel = 0;

/**
 * Variable: endingUpdate
 *
 * True if the program flow is currently inside endUpdate.
 */
mxGraphModel.prototype.endingUpdate = false;

/**
 * Function: clear
 *
 * Sets a new root using <createRoot>.
 */
mxGraphModel.prototype.clear = function () {
  this.setRoot(this.createRoot());
};

/**
 * Function: isCreateIds
 *
 * Returns <createIds>.
 */
mxGraphModel.prototype.isCreateIds = function () {
  return this.createIds;
};

/**
 * Function: setCreateIds
 *
 * Sets <createIds>.
 */
mxGraphModel.prototype.setCreateIds = function (value) {
  this.createIds = value;
};

/**
 * Function: createRoot
 *
 * Creates a new root cell with a default layer (child 0).
 */
mxGraphModel.prototype.createRoot = function () {
  var cell = new mxCell();
  cell.insert(new mxCell());

  return cell;
};

/**
 * Function: getCell
 *
 * Returns the <mxCell> for the specified Id or null if no cell can be
 * found for the given Id.
 *
 * Parameters:
 *
 * id - A string representing the Id of the cell.
 */
mxGraphModel.prototype.getCell = function (id) {
  return this.cells != null ? this.cells[id] : null;
};

/**
 * Function: filterCells
 *
 * Returns the cells from the given array where the given filter function
 * returns true.
 */
mxGraphModel.prototype.filterCells = function (cells, filter) {
  var result = null;

  if (cells != null) {
    result = [];

    for (var i = 0; i < cells.length; i++) {
      if (filter(cells[i])) {
        result.push(cells[i]);
      }
    }
  }

  return result;
};

/**
 * Function: getDescendants
 *
 * Returns all descendants of the given cell and the cell itself in an array.
 *
 * Parameters:
 *
 * parent - <mxCell> whose descendants should be returned.
 */
mxGraphModel.prototype.getDescendants = function (parent) {
  return this.filterDescendants(null, parent);
};

/**
 * Function: filterDescendants
 *
 * Visits all cells recursively and applies the specified filter function
 * to each cell. If the function returns true then the cell is added
 * to the resulting array. The parent and result paramters are optional.
 * If parent is not specified then the recursion starts at <root>.
 *
 * Example:
 * The following example extracts all vertices from a given model:
 * (code)
 * var filter = function(cell)
 * {
 * 	return model.isVertex(cell);
 * }
 * var vertices = model.filterDescendants(filter);
 * (end)
 *
 * Parameters:
 *
 * filter - JavaScript function that takes an <mxCell> as an argument
 * and returns a boolean.
 * parent - Optional <mxCell> that is used as the root of the recursion.
 */
mxGraphModel.prototype.filterDescendants = function (filter, parent) {
  // Creates a new array for storing the result
  var result = [];

  // Recursion starts at the root of the model
  parent = parent || this.getRoot();

  // Checks if the filter returns true for the cell
  // and adds it to the result array
  if (filter == null || filter(parent)) {
    result.push(parent);
  }

  // Visits the children of the cell
  var childCount = this.getChildCount(parent);

  for (var i = 0; i < childCount; i++) {
    var child = this.getChildAt(parent, i);
    result = result.concat(this.filterDescendants(filter, child));
  }

  return result;
};

/**
 * Function: getRoot
 *
 * Returns the root of the model or the topmost parent of the given cell.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> that specifies the child.
 */
mxGraphModel.prototype.getRoot = function (cell) {
  var root = cell || this.root;

  if (cell != null) {
    while (cell != null) {
      root = cell;
      cell = this.getParent(cell);
    }
  }

  return root;
};

/**
 * Function: setRoot
 *
 * Sets the <root> of the model using <mxRootChange> and adds the change to
 * the current transaction. This resets all datastructures in the model and
 * is the preferred way of clearing an existing model. Returns the new
 * root.
 *
 * Example:
 *
 * (code)
 * var root = new mxCell();
 * root.insert(new mxCell());
 * model.setRoot(root);
 * (end)
 *
 * Parameters:
 *
 * root - <mxCell> that specifies the new root.
 */
mxGraphModel.prototype.setRoot = function (root) {
  this.execute(new mxRootChange(this, root));

  return root;
};

/**
 * Function: rootChanged
 *
 * Inner callback to change the root of the model and update the internal
 * datastructures, such as <cells> and <nextId>. Returns the previous root.
 *
 * Parameters:
 *
 * root - <mxCell> that specifies the new root.
 */
mxGraphModel.prototype.rootChanged = function (root) {
  var oldRoot = this.root;
  this.root = root;

  // Resets counters and datastructures
  this.nextId = 0;
  this.cells = null;
  this.cellAdded(root);

  return oldRoot;
};

/**
 * Function: isRoot
 *
 * Returns true if the given cell is the root of the model and a non-null
 * value.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the possible root.
 */
mxGraphModel.prototype.isRoot = function (cell) {
  return cell != null && this.root == cell;
};

/**
 * Function: isLayer
 *
 * Returns true if <isRoot> returns true for the parent of the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the possible layer.
 */
mxGraphModel.prototype.isLayer = function (cell) {
  return this.isRoot(this.getParent(cell));
};

/**
 * Function: isAncestor
 *
 * Returns true if the given parent is an ancestor of the given child. Note
 * returns true if child == parent.
 *
 * Parameters:
 *
 * parent - <mxCell> that specifies the parent.
 * child - <mxCell> that specifies the child.
 */
mxGraphModel.prototype.isAncestor = function (parent, child) {
  while (child != null && child != parent) {
    child = this.getParent(child);
  }

  return child == parent;
};

/**
 * Function: contains
 *
 * Returns true if the model contains the given <mxCell>.
 *
 * Parameters:
 *
 * cell - <mxCell> that specifies the cell.
 */
mxGraphModel.prototype.contains = function (cell) {
  return this.isAncestor(this.root, cell);
};

/**
 * Function: getParent
 *
 * Returns the parent of the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> whose parent should be returned.
 */
mxGraphModel.prototype.getParent = function (cell) {
  return cell != null ? cell.getParent() : null;
};

/**
 * Function: add
 *
 * Adds the specified child to the parent at the given index using
 * <mxChildChange> and adds the change to the current transaction. If no
 * index is specified then the child is appended to the parent's array of
 * children. Returns the inserted child.
 *
 * Parameters:
 *
 * parent - <mxCell> that specifies the parent to contain the child.
 * child - <mxCell> that specifies the child to be inserted.
 * index - Optional integer that specifies the index of the child.
 */
mxGraphModel.prototype.add = function (parent, child, index) {
  if (child != parent && parent != null && child != null) {
    // Appends the child if no index was specified
    if (index == null) {
      index = this.getChildCount(parent);
    }

    var parentChanged = parent != this.getParent(child);
    this.execute(new mxChildChange(this, parent, child, index));

    // Maintains the edges parents by moving the edges
    // into the nearest common ancestor of its terminals
    if (this.maintainEdgeParent && parentChanged) {
      this.updateEdgeParents(child);
    }
  }

  return child;
};

/**
 * Function: cellAdded
 *
 * Inner callback to update <cells> when a cell has been added. This
 * implementation resolves collisions by creating new Ids. To change the
 * ID of a cell after it was inserted into the model, use the following
 * code:
 *
 * (code
 * delete model.cells[cell.getId()];
 * cell.setId(newId);
 * model.cells[cell.getId()] = cell;
 * (end)
 *
 * If the change of the ID should be part of the command history, then the
 * cell should be removed from the model and a clone with the new ID should
 * be reinserted into the model instead.
 *
 * Parameters:
 *
 * cell - <mxCell> that specifies the cell that has been added.
 */
mxGraphModel.prototype.cellAdded = function (cell) {
  if (cell != null) {
    // Creates an Id for the cell if not Id exists
    if (cell.getId() == null && this.createIds) {
      cell.setId(this.createId(cell));
    }

    if (cell.getId() != null) {
      var collision = this.getCell(cell.getId());

      if (collision != cell) {
        // Creates new Id for the cell
        // as long as there is a collision
        while (collision != null) {
          cell.setId(this.createId(cell));
          collision = this.getCell(cell.getId());
        }

        // Lazily creates the cells dictionary
        if (this.cells == null) {
          this.cells = new Object();
        }

        this.cells[cell.getId()] = cell;
      }
    }

    // Makes sure IDs of deleted cells are not reused
    if (mxUtils.isNumeric(cell.getId())) {
      this.nextId = Math.max(this.nextId, cell.getId());
    }

    // Recursively processes child cells
    var childCount = this.getChildCount(cell);

    for (var i = 0; i < childCount; i++) {
      this.cellAdded(this.getChildAt(cell, i));
    }
  }
};

/**
 * Function: createId
 *
 * Hook method to create an Id for the specified cell. This implementation
 * concatenates <prefix>, id and <postfix> to create the Id and increments
 * <nextId>. The cell is ignored by this implementation, but can be used in
 * overridden methods to prefix the Ids with eg. the cell type.
 *
 * Parameters:
 *
 * cell - <mxCell> to create the Id for.
 */
mxGraphModel.prototype.createId = function (cell) {
  var id = this.nextId;
  this.nextId++;

  return this.prefix + id + this.postfix;
};

/**
 * Function: updateEdgeParents
 *
 * Updates the parent for all edges that are connected to cell or one of
 * its descendants using <updateEdgeParent>.
 */
mxGraphModel.prototype.updateEdgeParents = function (cell, root) {
  // Gets the topmost node of the hierarchy
  root = root || this.getRoot(cell);

  // Updates edges on children first
  var childCount = this.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    var child = this.getChildAt(cell, i);
    this.updateEdgeParents(child, root);
  }

  // Updates the parents of all connected edges
  var edgeCount = this.getEdgeCount(cell);
  var edges = [];

  for (var i = 0; i < edgeCount; i++) {
    edges.push(this.getEdgeAt(cell, i));
  }

  for (var i = 0; i < edges.length; i++) {
    var edge = edges[i];

    // Updates edge parent if edge and child have
    // a common root node (does not need to be the
    // model root node)
    if (this.isAncestor(root, edge)) {
      this.updateEdgeParent(edge, root);
    }
  }
};

/**
 * Function: updateEdgeParent
 *
 * Inner callback to update the parent of the specified <mxCell> to the
 * nearest-common-ancestor of its two terminals.
 *
 * Parameters:
 *
 * edge - <mxCell> that specifies the edge.
 * root - <mxCell> that represents the current root of the model.
 */
mxGraphModel.prototype.updateEdgeParent = function (edge, root) {
  var source = this.getTerminal(edge, true);
  var target = this.getTerminal(edge, false);
  var cell = null;

  // Uses the first non-relative descendants of the source terminal
  while (source != null && !this.isEdge(source) && source.geometry != null && source.geometry.relative) {
    source = this.getParent(source);
  }

  // Uses the first non-relative descendants of the target terminal
  while (
    target != null &&
    this.ignoreRelativeEdgeParent &&
    !this.isEdge(target) &&
    target.geometry != null &&
    target.geometry.relative
  ) {
    target = this.getParent(target);
  }

  if (this.isAncestor(root, source) && this.isAncestor(root, target)) {
    if (source == target) {
      cell = this.getParent(source);
    } else {
      cell = this.getNearestCommonAncestor(source, target);
    }

    if (
      cell != null &&
      (this.getParent(cell) != this.root || this.isAncestor(cell, edge)) &&
      this.getParent(edge) != cell
    ) {
      var geo = this.getGeometry(edge);

      if (geo != null) {
        var origin1 = this.getOrigin(this.getParent(edge));
        var origin2 = this.getOrigin(cell);

        var dx = origin2.x - origin1.x;
        var dy = origin2.y - origin1.y;

        geo = geo.clone();
        geo.translate(-dx, -dy);
        this.setGeometry(edge, geo);
      }

      this.add(cell, edge, this.getChildCount(cell));
    }
  }
};

/**
 * Function: getOrigin
 *
 * Returns the absolute, accumulated origin for the children inside the
 * given parent as an <mxPoint>.
 */
mxGraphModel.prototype.getOrigin = function (cell) {
  var result = null;

  if (cell != null) {
    result = this.getOrigin(this.getParent(cell));

    if (!this.isEdge(cell)) {
      var geo = this.getGeometry(cell);

      if (geo != null) {
        result.x += geo.x;
        result.y += geo.y;
      }
    }
  } else {
    result = new mxPoint();
  }

  return result;
};

/**
 * Function: getNearestCommonAncestor
 *
 * Returns the nearest common ancestor for the specified cells.
 *
 * Parameters:
 *
 * cell1 - <mxCell> that specifies the first cell in the tree.
 * cell2 - <mxCell> that specifies the second cell in the tree.
 */
mxGraphModel.prototype.getNearestCommonAncestor = function (cell1, cell2) {
  if (cell1 != null && cell2 != null) {
    // Creates the cell path for the second cell
    var path = mxCellPath.create(cell2);

    if (path != null && path.length > 0) {
      // Bubbles through the ancestors of the first
      // cell to find the nearest common ancestor.
      var cell = cell1;
      var current = mxCellPath.create(cell);

      // Inverts arguments
      if (path.length < current.length) {
        cell = cell2;
        var tmp = current;
        current = path;
        path = tmp;
      }

      while (cell != null) {
        var parent = this.getParent(cell);

        // Checks if the cell path is equal to the beginning of the given cell path
        if (path.indexOf(current + mxCellPath.PATH_SEPARATOR) == 0 && parent != null) {
          return cell;
        }

        current = mxCellPath.getParentPath(current);
        cell = parent;
      }
    }
  }

  return null;
};

/**
 * Function: remove
 *
 * Removes the specified cell from the model using <mxChildChange> and adds
 * the change to the current transaction. This operation will remove the
 * cell and all of its children from the model. Returns the removed cell.
 *
 * Parameters:
 *
 * cell - <mxCell> that should be removed.
 */
mxGraphModel.prototype.remove = function (cell) {
  if (cell == this.root) {
    this.setRoot(null);
  } else if (this.getParent(cell) != null) {
    this.execute(new mxChildChange(this, null, cell));
  }

  return cell;
};

/**
 * Function: cellRemoved
 *
 * Inner callback to update <cells> when a cell has been removed.
 *
 * Parameters:
 *
 * cell - <mxCell> that specifies the cell that has been removed.
 */
mxGraphModel.prototype.cellRemoved = function (cell) {
  if (cell != null && this.cells != null) {
    // Recursively processes child cells
    var childCount = this.getChildCount(cell);

    for (var i = childCount - 1; i >= 0; i--) {
      this.cellRemoved(this.getChildAt(cell, i));
    }

    // Removes the dictionary entry for the cell
    if (this.cells != null && cell.getId() != null) {
      delete this.cells[cell.getId()];
    }
  }
};

/**
 * Function: parentForCellChanged
 *
 * Inner callback to update the parent of a cell using <mxCell.insert>
 * on the parent and return the previous parent.
 *
 * Parameters:
 *
 * cell - <mxCell> to update the parent for.
 * parent - <mxCell> that specifies the new parent of the cell.
 * index - Optional integer that defines the index of the child
 * in the parent's child array.
 */
mxGraphModel.prototype.parentForCellChanged = function (cell, parent, index) {
  var previous = this.getParent(cell);

  if (parent != null) {
    if (parent != previous || previous.getIndex(cell) != index) {
      parent.insert(cell, index);
    }
  } else if (previous != null) {
    var oldIndex = previous.getIndex(cell);
    previous.remove(oldIndex);
  }

  // Adds or removes the cell from the model
  var par = this.contains(parent);
  var pre = this.contains(previous);

  if (par && !pre) {
    this.cellAdded(cell);
  } else if (pre && !par) {
    this.cellRemoved(cell);
  }

  return previous;
};

/**
 * Function: getChildCount
 *
 * Returns the number of children in the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> whose number of children should be returned.
 */
mxGraphModel.prototype.getChildCount = function (cell) {
  return cell != null ? cell.getChildCount() : 0;
};

/**
 * Function: getChildAt
 *
 * Returns the child of the given <mxCell> at the given index.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the parent.
 * index - Integer that specifies the index of the child to be returned.
 */
mxGraphModel.prototype.getChildAt = function (cell, index) {
  return cell != null ? cell.getChildAt(index) : null;
};

/**
 * Function: getChildren
 *
 * Returns all children of the given <mxCell> as an array of <mxCells>. The
 * return value should be only be read.
 *
 * Parameters:
 *
 * cell - <mxCell> the represents the parent.
 */
mxGraphModel.prototype.getChildren = function (cell) {
  return cell != null ? cell.children : null;
};

/**
 * Function: getChildVertices
 *
 * Returns the child vertices of the given parent.
 *
 * Parameters:
 *
 * cell - <mxCell> whose child vertices should be returned.
 */
mxGraphModel.prototype.getChildVertices = function (parent) {
  return this.getChildCells(parent, true, false);
};

/**
 * Function: getChildEdges
 *
 * Returns the child edges of the given parent.
 *
 * Parameters:
 *
 * cell - <mxCell> whose child edges should be returned.
 */
mxGraphModel.prototype.getChildEdges = function (parent) {
  return this.getChildCells(parent, false, true);
};

/**
 * Function: getChildCells
 *
 * Returns the children of the given cell that are vertices and/or edges
 * depending on the arguments.
 *
 * Parameters:
 *
 * cell - <mxCell> the represents the parent.
 * vertices - Boolean indicating if child vertices should be returned.
 * Default is false.
 * edges - Boolean indicating if child edges should be returned.
 * Default is false.
 */
mxGraphModel.prototype.getChildCells = function (parent, vertices, edges) {
  vertices = vertices != null ? vertices : false;
  edges = edges != null ? edges : false;

  var childCount = this.getChildCount(parent);
  var result = [];

  for (var i = 0; i < childCount; i++) {
    var child = this.getChildAt(parent, i);

    if ((!edges && !vertices) || (edges && this.isEdge(child)) || (vertices && this.isVertex(child))) {
      result.push(child);
    }
  }

  return result;
};

/**
 * Function: getTerminal
 *
 * Returns the source or target <mxCell> of the given edge depending on the
 * value of the boolean parameter.
 *
 * Parameters:
 *
 * edge - <mxCell> that specifies the edge.
 * isSource - Boolean indicating which end of the edge should be returned.
 */
mxGraphModel.prototype.getTerminal = function (edge, isSource) {
  return edge != null ? edge.getTerminal(isSource) : null;
};

/**
 * Function: setTerminal
 *
 * Sets the source or target terminal of the given <mxCell> using
 * <mxTerminalChange> and adds the change to the current transaction.
 * This implementation updates the parent of the edge using <updateEdgeParent>
 * if required.
 *
 * Parameters:
 *
 * edge - <mxCell> that specifies the edge.
 * terminal - <mxCell> that specifies the new terminal.
 * isSource - Boolean indicating if the terminal is the new source or
 * target terminal of the edge.
 */
mxGraphModel.prototype.setTerminal = function (edge, terminal, isSource) {
  var terminalChanged = terminal != this.getTerminal(edge, isSource);
  this.execute(new mxTerminalChange(this, edge, terminal, isSource));

  if (this.maintainEdgeParent && terminalChanged) {
    this.updateEdgeParent(edge, this.getRoot());
  }

  return terminal;
};

/**
 * Function: setTerminals
 *
 * Sets the source and target <mxCell> of the given <mxCell> in a single
 * transaction using <setTerminal> for each end of the edge.
 *
 * Parameters:
 *
 * edge - <mxCell> that specifies the edge.
 * source - <mxCell> that specifies the new source terminal.
 * target - <mxCell> that specifies the new target terminal.
 */
mxGraphModel.prototype.setTerminals = function (edge, source, target) {
  this.beginUpdate();
  try {
    this.setTerminal(edge, source, true);
    this.setTerminal(edge, target, false);
  } finally {
    this.endUpdate();
  }
};

/**
 * Function: terminalForCellChanged
 *
 * Inner helper function to update the terminal of the edge using
 * <mxCell.insertEdge> and return the previous terminal.
 *
 * Parameters:
 *
 * edge - <mxCell> that specifies the edge to be updated.
 * terminal - <mxCell> that specifies the new terminal.
 * isSource - Boolean indicating if the terminal is the new source or
 * target terminal of the edge.
 */
mxGraphModel.prototype.terminalForCellChanged = function (edge, terminal, isSource) {
  var previous = this.getTerminal(edge, isSource);

  if (terminal != null) {
    terminal.insertEdge(edge, isSource);
  } else if (previous != null) {
    previous.removeEdge(edge, isSource);
  }

  return previous;
};

/**
 * Function: getEdgeCount
 *
 * Returns the number of distinct edges connected to the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the vertex.
 */
mxGraphModel.prototype.getEdgeCount = function (cell) {
  return cell != null ? cell.getEdgeCount() : 0;
};

/**
 * Function: getEdgeAt
 *
 * Returns the edge of cell at the given index.
 *
 * Parameters:
 *
 * cell - <mxCell> that specifies the vertex.
 * index - Integer that specifies the index of the edge
 * to return.
 */
mxGraphModel.prototype.getEdgeAt = function (cell, index) {
  return cell != null ? cell.getEdgeAt(index) : null;
};

/**
 * Function: getDirectedEdgeCount
 *
 * Returns the number of incoming or outgoing edges, ignoring the given
 * edge.
 *
 * Parameters:
 *
 * cell - <mxCell> whose edge count should be returned.
 * outgoing - Boolean that specifies if the number of outgoing or
 * incoming edges should be returned.
 * ignoredEdge - <mxCell> that represents an edge to be ignored.
 */
mxGraphModel.prototype.getDirectedEdgeCount = function (cell, outgoing, ignoredEdge) {
  var count = 0;
  var edgeCount = this.getEdgeCount(cell);

  for (var i = 0; i < edgeCount; i++) {
    var edge = this.getEdgeAt(cell, i);

    if (edge != ignoredEdge && this.getTerminal(edge, outgoing) == cell) {
      count++;
    }
  }

  return count;
};

/**
 * Function: getConnections
 *
 * Returns all edges of the given cell without loops.
 *
 * Parameters:
 *
 * cell - <mxCell> whose edges should be returned.
 *
 */
mxGraphModel.prototype.getConnections = function (cell) {
  return this.getEdges(cell, true, true, false);
};

/**
 * Function: getIncomingEdges
 *
 * Returns the incoming edges of the given cell without loops.
 *
 * Parameters:
 *
 * cell - <mxCell> whose incoming edges should be returned.
 *
 */
mxGraphModel.prototype.getIncomingEdges = function (cell) {
  return this.getEdges(cell, true, false, false);
};

/**
 * Function: getOutgoingEdges
 *
 * Returns the outgoing edges of the given cell without loops.
 *
 * Parameters:
 *
 * cell - <mxCell> whose outgoing edges should be returned.
 *
 */
mxGraphModel.prototype.getOutgoingEdges = function (cell) {
  return this.getEdges(cell, false, true, false);
};

/**
 * Function: getEdges
 *
 * Returns all distinct edges connected to this cell as a new array of
 * <mxCells>. If at least one of incoming or outgoing is true, then loops
 * are ignored, otherwise if both are false, then all edges connected to
 * the given cell are returned including loops.
 *
 * Parameters:
 *
 * cell - <mxCell> that specifies the cell.
 * incoming - Optional boolean that specifies if incoming edges should be
 * returned. Default is true.
 * outgoing - Optional boolean that specifies if outgoing edges should be
 * returned. Default is true.
 * includeLoops - Optional boolean that specifies if loops should be returned.
 * Default is true.
 */
mxGraphModel.prototype.getEdges = function (cell, incoming, outgoing, includeLoops) {
  incoming = incoming != null ? incoming : true;
  outgoing = outgoing != null ? outgoing : true;
  includeLoops = includeLoops != null ? includeLoops : true;

  var edgeCount = this.getEdgeCount(cell);
  var result = [];

  for (var i = 0; i < edgeCount; i++) {
    var edge = this.getEdgeAt(cell, i);
    var source = this.getTerminal(edge, true);
    var target = this.getTerminal(edge, false);

    if (
      (includeLoops && source == target) ||
      (source != target && ((incoming && target == cell) || (outgoing && source == cell)))
    ) {
      result.push(edge);
    }
  }

  return result;
};

/**
 * Function: getEdgesBetween
 *
 * Returns all edges between the given source and target pair. If directed
 * is true, then only edges from the source to the target are returned,
 * otherwise, all edges between the two cells are returned.
 *
 * Parameters:
 *
 * source - <mxCell> that defines the source terminal of the edge to be
 * returned.
 * target - <mxCell> that defines the target terminal of the edge to be
 * returned.
 * directed - Optional boolean that specifies if the direction of the
 * edge should be taken into account. Default is false.
 */
mxGraphModel.prototype.getEdgesBetween = function (source, target, directed) {
  directed = directed != null ? directed : false;

  var tmp1 = this.getEdgeCount(source);
  var tmp2 = this.getEdgeCount(target);

  // Assumes the source has less connected edges
  var terminal = source;
  var edgeCount = tmp1;

  // Uses the smaller array of connected edges
  // for searching the edge
  if (tmp2 < tmp1) {
    edgeCount = tmp2;
    terminal = target;
  }

  var result = [];

  // Checks if the edge is connected to the correct
  // cell and returns the first match
  for (var i = 0; i < edgeCount; i++) {
    var edge = this.getEdgeAt(terminal, i);
    var src = this.getTerminal(edge, true);
    var trg = this.getTerminal(edge, false);
    var directedMatch = src == source && trg == target;
    var oppositeMatch = trg == source && src == target;

    if (directedMatch || (!directed && oppositeMatch)) {
      result.push(edge);
    }
  }

  return result;
};

/**
 * Function: getOpposites
 *
 * Returns all opposite vertices wrt terminal for the given edges, only
 * returning sources and/or targets as specified. The result is returned
 * as an array of <mxCells>.
 *
 * Parameters:
 *
 * edges - Array of <mxCells> that contain the edges to be examined.
 * terminal - <mxCell> that specifies the known end of the edges.
 * sources - Boolean that specifies if source terminals should be contained
 * in the result. Default is true.
 * targets - Boolean that specifies if target terminals should be contained
 * in the result. Default is true.
 */
mxGraphModel.prototype.getOpposites = function (edges, terminal, sources, targets) {
  sources = sources != null ? sources : true;
  targets = targets != null ? targets : true;

  var terminals = [];

  if (edges != null) {
    for (var i = 0; i < edges.length; i++) {
      var source = this.getTerminal(edges[i], true);
      var target = this.getTerminal(edges[i], false);

      // Checks if the terminal is the source of
      // the edge and if the target should be
      // stored in the result
      if (source == terminal && target != null && target != terminal && targets) {
        terminals.push(target);
      }

      // Checks if the terminal is the taget of
      // the edge and if the source should be
      // stored in the result
      else if (target == terminal && source != null && source != terminal && sources) {
        terminals.push(source);
      }
    }
  }

  return terminals;
};

/**
 * Function: getTopmostCells
 *
 * Returns the topmost cells of the hierarchy in an array that contains no
 * descendants for each <mxCell> that it contains. Duplicates should be
 * removed in the cells array to improve performance.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose topmost ancestors should be returned.
 */
mxGraphModel.prototype.getTopmostCells = function (cells) {
  var dict = new mxDictionary();
  var tmp = [];

  for (var i = 0; i < cells.length; i++) {
    dict.put(cells[i], true);
  }

  for (var i = 0; i < cells.length; i++) {
    var cell = cells[i];
    var topmost = true;
    var parent = this.getParent(cell);

    while (parent != null) {
      if (dict.get(parent)) {
        topmost = false;
        break;
      }

      parent = this.getParent(parent);
    }

    if (topmost) {
      tmp.push(cell);
    }
  }

  return tmp;
};

/**
 * Function: isVertex
 *
 * Returns true if the given cell is a vertex.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the possible vertex.
 */
mxGraphModel.prototype.isVertex = function (cell) {
  return cell != null ? cell.isVertex() : false;
};

/**
 * Function: isEdge
 *
 * Returns true if the given cell is an edge.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the possible edge.
 */
mxGraphModel.prototype.isEdge = function (cell) {
  return cell != null ? cell.isEdge() : false;
};

/**
 * Function: isConnectable
 *
 * Returns true if the given <mxCell> is connectable. If <edgesConnectable>
 * is false, then this function returns false for all edges else it returns
 * the return value of <mxCell.isConnectable>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose connectable state should be returned.
 */
mxGraphModel.prototype.isConnectable = function (cell) {
  return cell != null ? cell.isConnectable() : false;
};

/**
 * Function: getValue
 *
 * Returns the user object of the given <mxCell> using <mxCell.getValue>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose user object should be returned.
 */
mxGraphModel.prototype.getValue = function (cell) {
  return cell != null ? cell.getValue() : null;
};

/**
 * Function: setValue
 *
 * Sets the user object of then given <mxCell> using <mxValueChange>
 * and adds the change to the current transaction.
 *
 * Parameters:
 *
 * cell - <mxCell> whose user object should be changed.
 * value - Object that defines the new user object.
 */
mxGraphModel.prototype.setValue = function (cell, value) {
  this.execute(new mxValueChange(this, cell, value));

  return value;
};

/**
 * Function: valueForCellChanged
 *
 * Inner callback to update the user object of the given <mxCell>
 * using <mxCell.valueChanged> and return the previous value,
 * that is, the return value of <mxCell.valueChanged>.
 *
 * To change a specific attribute in an XML node, the following code can be
 * used.
 *
 * (code)
 * graph.getModel().valueForCellChanged = function(cell, value)
 * {
 *   var previous = cell.value.getAttribute('label');
 *   cell.value.setAttribute('label', value);
 *
 *   return previous;
 * };
 * (end)
 */
mxGraphModel.prototype.valueForCellChanged = function (cell, value) {
  return cell.valueChanged(value);
};

/**
 * Function: getGeometry
 *
 * Returns the <mxGeometry> of the given <mxCell>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose geometry should be returned.
 */
mxGraphModel.prototype.getGeometry = function (cell) {
  return cell != null ? cell.getGeometry() : null;
};

/**
 * Function: setGeometry
 *
 * Sets the <mxGeometry> of the given <mxCell>. The actual update
 * of the cell is carried out in <geometryForCellChanged>. The
 * <mxGeometryChange> action is used to encapsulate the change.
 *
 * Parameters:
 *
 * cell - <mxCell> whose geometry should be changed.
 * geometry - <mxGeometry> that defines the new geometry.
 */
mxGraphModel.prototype.setGeometry = function (cell, geometry) {
  if (geometry != this.getGeometry(cell)) {
    this.execute(new mxGeometryChange(this, cell, geometry));
  }

  return geometry;
};

/**
 * Function: geometryForCellChanged
 *
 * Inner callback to update the <mxGeometry> of the given <mxCell> using
 * <mxCell.setGeometry> and return the previous <mxGeometry>.
 */
mxGraphModel.prototype.geometryForCellChanged = function (cell, geometry) {
  var previous = this.getGeometry(cell);
  cell.setGeometry(geometry);

  return previous;
};

/**
 * Function: getStyle
 *
 * Returns the style of the given <mxCell>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose style should be returned.
 */
mxGraphModel.prototype.getStyle = function (cell) {
  return cell != null ? cell.getStyle() : null;
};

/**
 * Function: setStyle
 *
 * Sets the style of the given <mxCell> using <mxStyleChange> and
 * adds the change to the current transaction.
 *
 * Parameters:
 *
 * cell - <mxCell> whose style should be changed.
 * style - String of the form [stylename;|key=value;] to specify
 * the new cell style.
 */
mxGraphModel.prototype.setStyle = function (cell, style) {
  if (style != this.getStyle(cell)) {
    this.execute(new mxStyleChange(this, cell, style));
  }

  return style;
};

/**
 * Function: styleForCellChanged
 *
 * Inner callback to update the style of the given <mxCell>
 * using <mxCell.setStyle> and return the previous style.
 *
 * Parameters:
 *
 * cell - <mxCell> that specifies the cell to be updated.
 * style - String of the form [stylename;|key=value;] to specify
 * the new cell style.
 */
mxGraphModel.prototype.styleForCellChanged = function (cell, style) {
  var previous = this.getStyle(cell);
  cell.setStyle(style);

  return previous;
};

/**
 * Function: isCollapsed
 *
 * Returns true if the given <mxCell> is collapsed.
 *
 * Parameters:
 *
 * cell - <mxCell> whose collapsed state should be returned.
 */
mxGraphModel.prototype.isCollapsed = function (cell) {
  return cell != null ? cell.isCollapsed() : false;
};

/**
 * Function: setCollapsed
 *
 * Sets the collapsed state of the given <mxCell> using <mxCollapseChange>
 * and adds the change to the current transaction.
 *
 * Parameters:
 *
 * cell - <mxCell> whose collapsed state should be changed.
 * collapsed - Boolean that specifies the new collpased state.
 */
mxGraphModel.prototype.setCollapsed = function (cell, collapsed) {
  if (collapsed != this.isCollapsed(cell)) {
    this.execute(new mxCollapseChange(this, cell, collapsed));
  }

  return collapsed;
};

/**
 * Function: collapsedStateForCellChanged
 *
 * Inner callback to update the collapsed state of the
 * given <mxCell> using <mxCell.setCollapsed> and return
 * the previous collapsed state.
 *
 * Parameters:
 *
 * cell - <mxCell> that specifies the cell to be updated.
 * collapsed - Boolean that specifies the new collpased state.
 */
mxGraphModel.prototype.collapsedStateForCellChanged = function (cell, collapsed) {
  var previous = this.isCollapsed(cell);
  cell.setCollapsed(collapsed);

  return previous;
};

/**
 * Function: isVisible
 *
 * Returns true if the given <mxCell> is visible.
 *
 * Parameters:
 *
 * cell - <mxCell> whose visible state should be returned.
 */
mxGraphModel.prototype.isVisible = function (cell) {
  return cell != null ? cell.isVisible() : false;
};

/**
 * Function: setVisible
 *
 * Sets the visible state of the given <mxCell> using <mxVisibleChange> and
 * adds the change to the current transaction.
 *
 * Parameters:
 *
 * cell - <mxCell> whose visible state should be changed.
 * visible - Boolean that specifies the new visible state.
 */
mxGraphModel.prototype.setVisible = function (cell, visible) {
  if (visible != this.isVisible(cell)) {
    this.execute(new mxVisibleChange(this, cell, visible));
  }

  return visible;
};

/**
 * Function: visibleStateForCellChanged
 *
 * Inner callback to update the visible state of the
 * given <mxCell> using <mxCell.setCollapsed> and return
 * the previous visible state.
 *
 * Parameters:
 *
 * cell - <mxCell> that specifies the cell to be updated.
 * visible - Boolean that specifies the new visible state.
 */
mxGraphModel.prototype.visibleStateForCellChanged = function (cell, visible) {
  var previous = this.isVisible(cell);
  cell.setVisible(visible);

  return previous;
};

/**
 * Function: execute
 *
 * Executes the given edit and fires events if required. The edit object
 * requires an execute function which is invoked. The edit is added to the
 * <currentEdit> between <beginUpdate> and <endUpdate> calls, so that
 * events will be fired if this execute is an individual transaction, that
 * is, if no previous <beginUpdate> calls have been made without calling
 * <endUpdate>. This implementation fires an <execute> event before
 * executing the given change.
 *
 * Parameters:
 *
 * change - Object that described the change.
 */
mxGraphModel.prototype.execute = function (change) {
  change.execute();
  this.beginUpdate();
  this.currentEdit.add(change);
  this.fireEvent(new mxEventObject(mxEvent.EXECUTE, 'change', change));
  // New global executed event
  this.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
  this.endUpdate();
};

/**
 * Function: beginUpdate
 *
 * Increments the <updateLevel> by one. The event notification
 * is queued until <updateLevel> reaches 0 by use of
 * <endUpdate>.
 *
 * All changes on <mxGraphModel> are transactional,
 * that is, they are executed in a single undoable change
 * on the model (without transaction isolation).
 * Therefore, if you want to combine any
 * number of changes into a single undoable change,
 * you should group any two or more API calls that
 * modify the graph model between <beginUpdate>
 * and <endUpdate> calls as shown here:
 *
 * (code)
 * var model = graph.getModel();
 * var parent = graph.getDefaultParent();
 * var index = model.getChildCount(parent);
 * model.beginUpdate();
 * try
 * {
 *   model.add(parent, v1, index);
 *   model.add(parent, v2, index+1);
 * }
 * finally
 * {
 *   model.endUpdate();
 * }
 * (end)
 *
 * Of course there is a shortcut for appending a
 * sequence of cells into the default parent:
 *
 * (code)
 * graph.addCells([v1, v2]).
 * (end)
 */
mxGraphModel.prototype.beginUpdate = function () {
  this.updateLevel++;
  this.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE));

  if (this.updateLevel == 1) {
    this.fireEvent(new mxEventObject(mxEvent.START_EDIT));
  }
};

/**
 * Function: endUpdate
 *
 * Decrements the <updateLevel> by one and fires an <undo>
 * event if the <updateLevel> reaches 0. This function
 * indirectly fires a <change> event by invoking the notify
 * function on the <currentEdit> und then creates a new
 * <currentEdit> using <createUndoableEdit>.
 *
 * The <undo> event is fired only once per edit, whereas
 * the <change> event is fired whenever the notify
 * function is invoked, that is, on undo and redo of
 * the edit.
 */
mxGraphModel.prototype.endUpdate = function () {
  this.updateLevel--;

  if (this.updateLevel == 0) {
    this.fireEvent(new mxEventObject(mxEvent.END_EDIT));
  }

  if (!this.endingUpdate) {
    this.endingUpdate = this.updateLevel == 0;
    this.fireEvent(new mxEventObject(mxEvent.END_UPDATE, 'edit', this.currentEdit));

    try {
      if (this.endingUpdate && !this.currentEdit.isEmpty()) {
        this.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO, 'edit', this.currentEdit));
        var tmp = this.currentEdit;
        this.currentEdit = this.createUndoableEdit();
        tmp.notify();
        this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', tmp));
      }
    } finally {
      this.endingUpdate = false;
    }
  }
};

/**
 * Function: createUndoableEdit
 *
 * Creates a new <mxUndoableEdit> that implements the
 * notify function to fire a <change> and <notify> event
 * through the <mxUndoableEdit>'s source.
 *
 * Parameters:
 *
 * significant - Optional boolean that specifies if the edit to be created is
 * significant. Default is true.
 */
mxGraphModel.prototype.createUndoableEdit = function (significant) {
  var edit = new mxUndoableEdit(this, significant != null ? significant : true);

  edit.notify = function () {
    // LATER: Remove changes property (deprecated)
    edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE, 'edit', edit, 'changes', edit.changes));
    edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY, 'edit', edit, 'changes', edit.changes));
  };

  return edit;
};

/**
 * Function: mergeChildren
 *
 * Merges the children of the given cell into the given target cell inside
 * this model. All cells are cloned unless there is a corresponding cell in
 * the model with the same id, in which case the source cell is ignored and
 * all edges are connected to the corresponding cell in this model. Edges
 * are considered to have no identity and are always cloned unless the
 * cloneAllEdges flag is set to false, in which case edges with the same
 * id in the target model are reconnected to reflect the terminals of the
 * source edges.
 */
mxGraphModel.prototype.mergeChildren = function (from, to, cloneAllEdges) {
  cloneAllEdges = cloneAllEdges != null ? cloneAllEdges : true;

  this.beginUpdate();
  try {
    var mapping = new Object();
    this.mergeChildrenImpl(from, to, cloneAllEdges, mapping);

    // Post-processes all edges in the mapping and
    // reconnects the terminals to the corresponding
    // cells in the target model
    for (var key in mapping) {
      var cell = mapping[key];
      var terminal = this.getTerminal(cell, true);

      if (terminal != null) {
        terminal = mapping[mxCellPath.create(terminal)];
        this.setTerminal(cell, terminal, true);
      }

      terminal = this.getTerminal(cell, false);

      if (terminal != null) {
        terminal = mapping[mxCellPath.create(terminal)];
        this.setTerminal(cell, terminal, false);
      }
    }
  } finally {
    this.endUpdate();
  }
};

/**
 * Function: mergeChildren
 *
 * Clones the children of the source cell into the given target cell in
 * this model and adds an entry to the mapping that maps from the source
 * cell to the target cell with the same id or the clone of the source cell
 * that was inserted into this model.
 */
mxGraphModel.prototype.mergeChildrenImpl = function (from, to, cloneAllEdges, mapping) {
  this.beginUpdate();
  try {
    var childCount = from.getChildCount();

    for (var i = 0; i < childCount; i++) {
      var cell = from.getChildAt(i);

      if (typeof cell.getId == 'function') {
        var id = cell.getId();
        var target = id != null && (!this.isEdge(cell) || !cloneAllEdges) ? this.getCell(id) : null;

        // Clones and adds the child if no cell exists for the id
        if (target == null) {
          var clone = cell.clone();
          clone.setId(id);

          // Sets the terminals from the original cell to the clone
          // because the lookup uses strings not cells in JS
          clone.setTerminal(cell.getTerminal(true), true);
          clone.setTerminal(cell.getTerminal(false), false);

          // Do *NOT* use model.add as this will move the edge away
          // from the parent in updateEdgeParent if maintainEdgeParent
          // is enabled in the target model
          target = to.insert(clone);
          this.cellAdded(target);
        }

        // Stores the mapping for later reconnecting edges
        mapping[mxCellPath.create(cell)] = target;

        // Recurses
        this.mergeChildrenImpl(cell, target, cloneAllEdges, mapping);
      }
    }
  } finally {
    this.endUpdate();
  }
};

/**
 * Function: getParents
 *
 * Returns an array that represents the set (no duplicates) of all parents
 * for the given array of cells.
 *
 * Parameters:
 *
 * cells - Array of cells whose parents should be returned.
 */
mxGraphModel.prototype.getParents = function (cells) {
  var parents = [];

  if (cells != null) {
    var dict = new mxDictionary();

    for (var i = 0; i < cells.length; i++) {
      var parent = this.getParent(cells[i]);

      if (parent != null && !dict.get(parent)) {
        dict.put(parent, true);
        parents.push(parent);
      }
    }
  }

  return parents;
};

//
// Cell Cloning
//

/**
 * Function: cloneCell
 *
 * Returns a deep clone of the given <mxCell> (including
 * the children) which is created using <cloneCells>.
 *
 * Parameters:
 *
 * cell - <mxCell> to be cloned.
 * includeChildren - Optional boolean indicating if the cells should be cloned
 * with all descendants. Default is true.
 */
mxGraphModel.prototype.cloneCell = function (cell, includeChildren) {
  if (cell != null) {
    return this.cloneCells([cell], includeChildren)[0];
  }

  return null;
};

/**
 * Function: cloneCells
 *
 * Returns an array of clones for the given array of <mxCells>.
 * Depending on the value of includeChildren, a deep clone is created for
 * each cell. Connections are restored based if the corresponding
 * cell is contained in the passed in array.
 *
 * Parameters:
 *
 * cells - Array of <mxCell> to be cloned.
 * includeChildren - Optional boolean indicating if the cells should be cloned
 * with all descendants. Default is true.
 * mapping - Optional mapping for existing clones.
 */
mxGraphModel.prototype.cloneCells = function (cells, includeChildren, mapping) {
  includeChildren = includeChildren != null ? includeChildren : true;
  mapping = mapping != null ? mapping : new Object();
  var clones = [];

  for (var i = 0; i < cells.length; i++) {
    if (cells[i] != null) {
      clones.push(this.cloneCellImpl(cells[i], mapping, includeChildren));
    } else {
      clones.push(null);
    }
  }

  for (var i = 0; i < clones.length; i++) {
    if (clones[i] != null) {
      this.restoreClone(clones[i], cells[i], mapping);
    }
  }

  return clones;
};

/**
 * Function: cloneCellImpl
 *
 * Inner helper method for cloning cells recursively.
 */
mxGraphModel.prototype.cloneCellImpl = function (cell, mapping, includeChildren) {
  var ident = mxObjectIdentity.get(cell);
  var clone = mapping[ident];

  if (clone == null) {
    clone = this.cellCloned(cell);
    mapping[ident] = clone;

    if (includeChildren) {
      var childCount = this.getChildCount(cell);

      for (var i = 0; i < childCount; i++) {
        var cloneChild = this.cloneCellImpl(this.getChildAt(cell, i), mapping, true);
        clone.insert(cloneChild);
      }
    }
  }

  return clone;
};

/**
 * Function: cellCloned
 *
 * Hook for cloning the cell. This returns cell.clone() or
 * any possible exceptions.
 */
mxGraphModel.prototype.cellCloned = function (cell) {
  return cell.clone();
};

/**
 * Function: restoreClone
 *
 * Inner helper method for restoring the connections in
 * a network of cloned cells.
 */
mxGraphModel.prototype.restoreClone = function (clone, cell, mapping) {
  var source = this.getTerminal(cell, true);

  if (source != null) {
    var tmp = mapping[mxObjectIdentity.get(source)];

    if (tmp != null) {
      tmp.insertEdge(clone, true);
    }
  }

  var target = this.getTerminal(cell, false);

  if (target != null) {
    var tmp = mapping[mxObjectIdentity.get(target)];

    if (tmp != null) {
      tmp.insertEdge(clone, false);
    }
  }

  var childCount = this.getChildCount(clone);

  for (var i = 0; i < childCount; i++) {
    this.restoreClone(this.getChildAt(clone, i), this.getChildAt(cell, i), mapping);
  }
};

//
// Atomic changes
//

/**
 * Class: mxRootChange
 *
 * Action to change the root in a model.
 *
 * Constructor: mxRootChange
 *
 * Constructs a change of the root in the
 * specified model.
 */
function mxRootChange(model, root) {
  this.model = model;
  this.root = root;
  this.previous = root;
}

/**
 * Function: execute
 *
 * Carries out a change of the root using
 * <mxGraphModel.rootChanged>.
 */
mxRootChange.prototype.execute = function () {
  this.root = this.previous;
  this.previous = this.model.rootChanged(this.previous);
};

/**
 * Class: mxChildChange
 *
 * Action to add or remove a child in a model.
 *
 * Constructor: mxChildChange
 *
 * Constructs a change of a child in the
 * specified model.
 */
function mxChildChange(model, parent, child, index) {
  this.model = model;
  this.parent = parent;
  this.previous = parent;
  this.child = child;
  this.index = index;
  this.previousIndex = index;
}

/**
 * Function: execute
 *
 * Changes the parent of <child> using
 * <mxGraphModel.parentForCellChanged> and
 * removes or restores the cell's
 * connections.
 */
mxChildChange.prototype.execute = function () {
  if (this.child != null) {
    var tmp = this.model.getParent(this.child);
    var tmp2 = tmp != null ? tmp.getIndex(this.child) : 0;

    if (this.previous == null) {
      this.connect(this.child, false);
    }

    tmp = this.model.parentForCellChanged(this.child, this.previous, this.previousIndex);

    if (this.previous != null) {
      this.connect(this.child, true);
    }

    this.parent = this.previous;
    this.previous = tmp;
    this.index = this.previousIndex;
    this.previousIndex = tmp2;
  }
};

/**
 * Function: disconnect
 *
 * Disconnects the given cell recursively from its
 * terminals and stores the previous terminal in the
 * cell's terminals.
 */
mxChildChange.prototype.connect = function (cell, isConnect) {
  isConnect = isConnect != null ? isConnect : true;

  var source = cell.getTerminal(true);
  var target = cell.getTerminal(false);

  if (source != null) {
    if (isConnect) {
      this.model.terminalForCellChanged(cell, source, true);
    } else {
      this.model.terminalForCellChanged(cell, null, true);
    }
  }

  if (target != null) {
    if (isConnect) {
      this.model.terminalForCellChanged(cell, target, false);
    } else {
      this.model.terminalForCellChanged(cell, null, false);
    }
  }

  cell.setTerminal(source, true);
  cell.setTerminal(target, false);

  var childCount = this.model.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    this.connect(this.model.getChildAt(cell, i), isConnect);
  }
};

/**
 * Class: mxTerminalChange
 *
 * Action to change a terminal in a model.
 *
 * Constructor: mxTerminalChange
 *
 * Constructs a change of a terminal in the
 * specified model.
 */
function mxTerminalChange(model, cell, terminal, source) {
  this.model = model;
  this.cell = cell;
  this.terminal = terminal;
  this.previous = terminal;
  this.source = source;
}

/**
 * Function: execute
 *
 * Changes the terminal of <cell> to <previous> using
 * <mxGraphModel.terminalForCellChanged>.
 */
mxTerminalChange.prototype.execute = function () {
  if (this.cell != null) {
    this.terminal = this.previous;
    this.previous = this.model.terminalForCellChanged(this.cell, this.previous, this.source);
  }
};

/**
 * Class: mxValueChange
 *
 * Action to change a user object in a model.
 *
 * Constructor: mxValueChange
 *
 * Constructs a change of a user object in the
 * specified model.
 */
function mxValueChange(model, cell, value) {
  this.model = model;
  this.cell = cell;
  this.value = value;
  this.previous = value;
}

/**
 * Function: execute
 *
 * Changes the value of <cell> to <previous> using
 * <mxGraphModel.valueForCellChanged>.
 */
mxValueChange.prototype.execute = function () {
  if (this.cell != null) {
    this.value = this.previous;
    this.previous = this.model.valueForCellChanged(this.cell, this.previous);
  }
};

/**
 * Class: mxStyleChange
 *
 * Action to change a cell's style in a model.
 *
 * Constructor: mxStyleChange
 *
 * Constructs a change of a style in the
 * specified model.
 */
function mxStyleChange(model, cell, style) {
  this.model = model;
  this.cell = cell;
  this.style = style;
  this.previous = style;
}

/**
 * Function: execute
 *
 * Changes the style of <cell> to <previous> using
 * <mxGraphModel.styleForCellChanged>.
 */
mxStyleChange.prototype.execute = function () {
  if (this.cell != null) {
    this.style = this.previous;
    this.previous = this.model.styleForCellChanged(this.cell, this.previous);
  }
};

/**
 * Class: mxGeometryChange
 *
 * Action to change a cell's geometry in a model.
 *
 * Constructor: mxGeometryChange
 *
 * Constructs a change of a geometry in the
 * specified model.
 */
function mxGeometryChange(model, cell, geometry) {
  this.model = model;
  this.cell = cell;
  this.geometry = geometry;
  this.previous = geometry;
}

/**
 * Function: execute
 *
 * Changes the geometry of <cell> ro <previous> using
 * <mxGraphModel.geometryForCellChanged>.
 */
mxGeometryChange.prototype.execute = function () {
  if (this.cell != null) {
    this.geometry = this.previous;
    this.previous = this.model.geometryForCellChanged(this.cell, this.previous);
  }
};

/**
 * Class: mxCollapseChange
 *
 * Action to change a cell's collapsed state in a model.
 *
 * Constructor: mxCollapseChange
 *
 * Constructs a change of a collapsed state in the
 * specified model.
 */
function mxCollapseChange(model, cell, collapsed) {
  this.model = model;
  this.cell = cell;
  this.collapsed = collapsed;
  this.previous = collapsed;
}

/**
 * Function: execute
 *
 * Changes the collapsed state of <cell> to <previous> using
 * <mxGraphModel.collapsedStateForCellChanged>.
 */
mxCollapseChange.prototype.execute = function () {
  if (this.cell != null) {
    this.collapsed = this.previous;
    this.previous = this.model.collapsedStateForCellChanged(this.cell, this.previous);
  }
};

/**
 * Class: mxVisibleChange
 *
 * Action to change a cell's visible state in a model.
 *
 * Constructor: mxVisibleChange
 *
 * Constructs a change of a visible state in the
 * specified model.
 */
function mxVisibleChange(model, cell, visible) {
  this.model = model;
  this.cell = cell;
  this.visible = visible;
  this.previous = visible;
}

/**
 * Function: execute
 *
 * Changes the visible state of <cell> to <previous> using
 * <mxGraphModel.visibleStateForCellChanged>.
 */
mxVisibleChange.prototype.execute = function () {
  if (this.cell != null) {
    this.visible = this.previous;
    this.previous = this.model.visibleStateForCellChanged(this.cell, this.previous);
  }
};

/**
 * Class: mxCellAttributeChange
 *
 * Action to change the attribute of a cell's user object.
 * There is no method on the graph model that uses this
 * action. To use the action, you can use the code shown
 * in the example below.
 *
 * Example:
 *
 * To change the attributeName in the cell's user object
 * to attributeValue, use the following code:
 *
 * (code)
 * model.beginUpdate();
 * try
 * {
 *   var edit = new mxCellAttributeChange(
 *     cell, attributeName, attributeValue);
 *   model.execute(edit);
 * }
 * finally
 * {
 *   model.endUpdate();
 * }
 * (end)
 *
 * Constructor: mxCellAttributeChange
 *
 * Constructs a change of a attribute of the DOM node
 * stored as the value of the given <mxCell>.
 */
function mxCellAttributeChange(cell, attribute, value) {
  this.cell = cell;
  this.attribute = attribute;
  this.value = value;
  this.previous = value;
}

/**
 * Function: execute
 *
 * Changes the attribute of the cell's user object by
 * using <mxCell.setAttribute>.
 */
mxCellAttributeChange.prototype.execute = function () {
  if (this.cell != null) {
    var tmp = this.cell.getAttribute(this.attribute);

    if (this.previous == null) {
      this.cell.value.removeAttribute(this.attribute);
    } else {
      this.cell.setAttribute(this.attribute, this.previous);
    }

    this.previous = tmp;
  }
};

/**
 * model/mxCell.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCell
 *
 * Cells are the elements of the graph model. They represent the state
 * of the groups, vertices and edges in a graph.
 *
 * Custom attributes:
 *
 * For custom attributes we recommend using an XML node as the value of a cell.
 * The following code can be used to create a cell with an XML node as the
 * value:
 *
 * (code)
 * var doc = mxUtils.createXmlDocument();
 * var node = doc.createElement('MyNode')
 * node.setAttribute('label', 'MyLabel');
 * node.setAttribute('attribute1', 'value1');
 * graph.insertVertex(graph.getDefaultParent(), null, node, 40, 40, 80, 30);
 * (end)
 *
 * For the label to work, <mxGraph.convertValueToString> and
 * <mxGraph.cellLabelChanged> should be overridden as follows:
 *
 * (code)
 * graph.convertValueToString = function(cell)
 * {
 *   if (mxUtils.isNode(cell.value))
 *   {
 *     return cell.getAttribute('label', '')
 *   }
 * };
 *
 * var cellLabelChanged = graph.cellLabelChanged;
 * graph.cellLabelChanged = function(cell, newValue, autoSize)
 * {
 *   if (mxUtils.isNode(cell.value))
 *   {
 *     // Clones the value for correct undo/redo
 *     var elt = cell.value.cloneNode(true);
 *     elt.setAttribute('label', newValue);
 *     newValue = elt;
 *   }
 *
 *   cellLabelChanged.apply(this, arguments);
 * };
 * (end)
 *
 * Callback: onInit
 *
 * Called from within the constructor.
 *
 * Constructor: mxCell
 *
 * Constructs a new cell to be used in a graph model.
 * This method invokes <onInit> upon completion.
 *
 * Parameters:
 *
 * value - Optional object that represents the cell value.
 * geometry - Optional <mxGeometry> that specifies the geometry.
 * style - Optional formatted string that defines the style.
 */
function mxCell(value, geometry, style) {
  this.value = value;
  this.setGeometry(geometry);
  this.setStyle(style);

  if (this.onInit != null) {
    this.onInit();
  }
}

/**
 * Variable: id
 *
 * Holds the Id. Default is null.
 */
mxCell.prototype.id = null;

/**
 * Variable: value
 *
 * Holds the user object. Default is null.
 */
mxCell.prototype.value = null;

/**
 * Variable: geometry
 *
 * Holds the <mxGeometry>. Default is null.
 */
mxCell.prototype.geometry = null;

/**
 * Variable: style
 *
 * Holds the style as a string of the form [(stylename|key=value);]. Default is
 * null.
 */
mxCell.prototype.style = null;

/**
 * Variable: vertex
 *
 * Specifies whether the cell is a vertex. Default is false.
 */
mxCell.prototype.vertex = false;

/**
 * Variable: edge
 *
 * Specifies whether the cell is an edge. Default is false.
 */
mxCell.prototype.edge = false;

/**
 * Variable: connectable
 *
 * Specifies whether the cell is connectable. Default is true.
 */
mxCell.prototype.connectable = true;

/**
 * Variable: visible
 *
 * Specifies whether the cell is visible. Default is true.
 */
mxCell.prototype.visible = true;

/**
 * Variable: collapsed
 *
 * Specifies whether the cell is collapsed. Default is false.
 */
mxCell.prototype.collapsed = false;

/**
 * Variable: parent
 *
 * Reference to the parent cell.
 */
mxCell.prototype.parent = null;

/**
 * Variable: source
 *
 * Reference to the source terminal.
 */
mxCell.prototype.source = null;

/**
 * Variable: target
 *
 * Reference to the target terminal.
 */
mxCell.prototype.target = null;

/**
 * Variable: children
 *
 * Holds the child cells.
 */
mxCell.prototype.children = null;

/**
 * Variable: edges
 *
 * Holds the edges.
 */
mxCell.prototype.edges = null;

/**
 * Variable: mxTransient
 *
 * List of members that should not be cloned inside <clone>. This field is
 * passed to <mxUtils.clone> and is not made persistent in <mxCellCodec>.
 * This is not a convention for all classes, it is only used in this class
 * to mark transient fields since transient modifiers are not supported by
 * the language.
 */
mxCell.prototype.mxTransient = ['id', 'value', 'parent', 'source', 'target', 'children', 'edges'];

/**
 * Function: getId
 *
 * Returns the Id of the cell as a string.
 */
mxCell.prototype.getId = function () {
  return this.id;
};

/**
 * Function: setId
 *
 * Sets the Id of the cell to the given string.
 */
mxCell.prototype.setId = function (id) {
  this.id = id;
};

/**
 * Function: getValue
 *
 * Returns the user object of the cell. The user
 * object is stored in <value>.
 */
mxCell.prototype.getValue = function () {
  return this.value;
};

/**
 * Function: setValue
 *
 * Sets the user object of the cell. The user object
 * is stored in <value>.
 */
mxCell.prototype.setValue = function (value) {
  this.value = value;
};

/**
 * Function: valueChanged
 *
 * Changes the user object after an in-place edit
 * and returns the previous value. This implementation
 * replaces the user object with the given value and
 * returns the old user object.
 */
mxCell.prototype.valueChanged = function (newValue) {
  var previous = this.getValue();
  this.setValue(newValue);

  return previous;
};

/**
 * Function: getGeometry
 *
 * Returns the <mxGeometry> that describes the <geometry>.
 */
mxCell.prototype.getGeometry = function () {
  return this.geometry;
};

/**
 * Function: setGeometry
 *
 * Sets the <mxGeometry> to be used as the <geometry>.
 */
mxCell.prototype.setGeometry = function (geometry) {
  this.geometry = geometry;
};

/**
 * Function: getStyle
 *
 * Returns a string that describes the <style>.
 */
mxCell.prototype.getStyle = function () {
  return this.style;
};

/**
 * Function: setStyle
 *
 * Sets the string to be used as the <style>.
 */
mxCell.prototype.setStyle = function (style) {
  this.style = style;
};

/**
 * Function: isVertex
 *
 * Returns true if the cell is a vertex.
 */
mxCell.prototype.isVertex = function () {
  return this.vertex != 0;
};

/**
 * Function: setVertex
 *
 * Specifies if the cell is a vertex. This should only be assigned at
 * construction of the cell and not be changed during its lifecycle.
 *
 * Parameters:
 *
 * vertex - Boolean that specifies if the cell is a vertex.
 */
mxCell.prototype.setVertex = function (vertex) {
  this.vertex = vertex;
};

/**
 * Function: isEdge
 *
 * Returns true if the cell is an edge.
 */
mxCell.prototype.isEdge = function () {
  return this.edge != 0;
};

/**
 * Function: setEdge
 *
 * Specifies if the cell is an edge. This should only be assigned at
 * construction of the cell and not be changed during its lifecycle.
 *
 * Parameters:
 *
 * edge - Boolean that specifies if the cell is an edge.
 */
mxCell.prototype.setEdge = function (edge) {
  this.edge = edge;
};

/**
 * Function: isConnectable
 *
 * Returns true if the cell is connectable.
 */
mxCell.prototype.isConnectable = function () {
  return this.connectable != 0;
};

/**
 * Function: setConnectable
 *
 * Sets the connectable state.
 *
 * Parameters:
 *
 * connectable - Boolean that specifies the new connectable state.
 */
mxCell.prototype.setConnectable = function (connectable) {
  this.connectable = connectable;
};

/**
 * Function: isVisible
 *
 * Returns true if the cell is visibile.
 */
mxCell.prototype.isVisible = function () {
  return this.visible != 0;
};

/**
 * Function: setVisible
 *
 * Specifies if the cell is visible.
 *
 * Parameters:
 *
 * visible - Boolean that specifies the new visible state.
 */
mxCell.prototype.setVisible = function (visible) {
  this.visible = visible;
};

/**
 * Function: isCollapsed
 *
 * Returns true if the cell is collapsed.
 */
mxCell.prototype.isCollapsed = function () {
  return this.collapsed != 0;
};

/**
 * Function: setCollapsed
 *
 * Sets the collapsed state.
 *
 * Parameters:
 *
 * collapsed - Boolean that specifies the new collapsed state.
 */
mxCell.prototype.setCollapsed = function (collapsed) {
  this.collapsed = collapsed;
};

/**
 * Function: getParent
 *
 * Returns the cell's parent.
 */
mxCell.prototype.getParent = function () {
  return this.parent;
};

/**
 * Function: setParent
 *
 * Sets the parent cell.
 *
 * Parameters:
 *
 * parent - <mxCell> that represents the new parent.
 */
mxCell.prototype.setParent = function (parent) {
  this.parent = parent;
};

/**
 * Function: getTerminal
 *
 * Returns the source or target terminal.
 *
 * Parameters:
 *
 * source - Boolean that specifies if the source terminal should be
 * returned.
 */
mxCell.prototype.getTerminal = function (source) {
  return source ? this.source : this.target;
};

/**
 * Function: setTerminal
 *
 * Sets the source or target terminal and returns the new terminal.
 *
 * Parameters:
 *
 * terminal - <mxCell> that represents the new source or target terminal.
 * isSource - Boolean that specifies if the source or target terminal
 * should be set.
 */
mxCell.prototype.setTerminal = function (terminal, isSource) {
  if (isSource) {
    this.source = terminal;
  } else {
    this.target = terminal;
  }

  return terminal;
};

/**
 * Function: getChildCount
 *
 * Returns the number of child cells.
 */
mxCell.prototype.getChildCount = function () {
  return this.children == null ? 0 : this.children.length;
};

/**
 * Function: getIndex
 *
 * Returns the index of the specified child in the child array.
 *
 * Parameters:
 *
 * child - Child whose index should be returned.
 */
mxCell.prototype.getIndex = function (child) {
  return mxUtils.indexOf(this.children, child);
};

/**
 * Function: getChildAt
 *
 * Returns the child at the specified index.
 *
 * Parameters:
 *
 * index - Integer that specifies the child to be returned.
 */
mxCell.prototype.getChildAt = function (index) {
  return this.children == null ? null : this.children[index];
};

/**
 * Function: insert
 *
 * Inserts the specified child into the child array at the specified index
 * and updates the parent reference of the child. If not childIndex is
 * specified then the child is appended to the child array. Returns the
 * inserted child.
 *
 * Parameters:
 *
 * child - <mxCell> to be inserted or appended to the child array.
 * index - Optional integer that specifies the index at which the child
 * should be inserted into the child array.
 */
mxCell.prototype.insert = function (child, index) {
  if (child != null) {
    if (index == null) {
      index = this.getChildCount();

      if (child.getParent() == this) {
        index--;
      }
    }

    child.removeFromParent();
    child.setParent(this);

    if (this.children == null) {
      this.children = [];
      this.children.push(child);
    } else {
      this.children.splice(index, 0, child);
    }
  }

  return child;
};

/**
 * Function: remove
 *
 * Removes the child at the specified index from the child array and
 * returns the child that was removed. Will remove the parent reference of
 * the child.
 *
 * Parameters:
 *
 * index - Integer that specifies the index of the child to be
 * removed.
 */
mxCell.prototype.remove = function (index) {
  var child = null;

  if (this.children != null && index >= 0) {
    child = this.getChildAt(index);

    if (child != null) {
      this.children.splice(index, 1);
      child.setParent(null);
    }
  }

  return child;
};

/**
 * Function: removeFromParent
 *
 * Removes the cell from its parent.
 */
mxCell.prototype.removeFromParent = function () {
  if (this.parent != null) {
    var index = this.parent.getIndex(this);
    this.parent.remove(index);
  }
};

/**
 * Function: getEdgeCount
 *
 * Returns the number of edges in the edge array.
 */
mxCell.prototype.getEdgeCount = function () {
  return this.edges == null ? 0 : this.edges.length;
};

/**
 * Function: getEdgeIndex
 *
 * Returns the index of the specified edge in <edges>.
 *
 * Parameters:
 *
 * edge - <mxCell> whose index in <edges> should be returned.
 */
mxCell.prototype.getEdgeIndex = function (edge) {
  return mxUtils.indexOf(this.edges, edge);
};

/**
 * Function: getEdgeAt
 *
 * Returns the edge at the specified index in <edges>.
 *
 * Parameters:
 *
 * index - Integer that specifies the index of the edge to be returned.
 */
mxCell.prototype.getEdgeAt = function (index) {
  return this.edges == null ? null : this.edges[index];
};

/**
 * Function: insertEdge
 *
 * Inserts the specified edge into the edge array and returns the edge.
 * Will update the respective terminal reference of the edge.
 *
 * Parameters:
 *
 * edge - <mxCell> to be inserted into the edge array.
 * isOutgoing - Boolean that specifies if the edge is outgoing.
 */
mxCell.prototype.insertEdge = function (edge, isOutgoing) {
  if (edge != null) {
    edge.removeFromTerminal(isOutgoing);
    edge.setTerminal(this, isOutgoing);

    if (this.edges == null || edge.getTerminal(!isOutgoing) != this || mxUtils.indexOf(this.edges, edge) < 0) {
      if (this.edges == null) {
        this.edges = [];
      }

      this.edges.push(edge);
    }
  }

  return edge;
};

/**
 * Function: removeEdge
 *
 * Removes the specified edge from the edge array and returns the edge.
 * Will remove the respective terminal reference from the edge.
 *
 * Parameters:
 *
 * edge - <mxCell> to be removed from the edge array.
 * isOutgoing - Boolean that specifies if the edge is outgoing.
 */
mxCell.prototype.removeEdge = function (edge, isOutgoing) {
  if (edge != null) {
    if (edge.getTerminal(!isOutgoing) != this && this.edges != null) {
      var index = this.getEdgeIndex(edge);

      if (index >= 0) {
        this.edges.splice(index, 1);
      }
    }

    edge.setTerminal(null, isOutgoing);
  }

  return edge;
};

/**
 * Function: removeFromTerminal
 *
 * Removes the edge from its source or target terminal.
 *
 * Parameters:
 *
 * isSource - Boolean that specifies if the edge should be removed from its
 * source or target terminal.
 */
mxCell.prototype.removeFromTerminal = function (isSource) {
  var terminal = this.getTerminal(isSource);

  if (terminal != null) {
    terminal.removeEdge(this, isSource);
  }
};

/**
 * Function: hasAttribute
 *
 * Returns true if the user object is an XML node that contains the given
 * attribute.
 *
 * Parameters:
 *
 * name - Name of the attribute.
 */
mxCell.prototype.hasAttribute = function (name) {
  var userObject = this.getValue();

  return userObject != null && userObject.nodeType == mxConstants.NODETYPE_ELEMENT && userObject.hasAttribute
    ? userObject.hasAttribute(name)
    : userObject.getAttribute(name) != null;
};

/**
 * Function: getAttribute
 *
 * Returns the specified attribute from the user object if it is an XML
 * node.
 *
 * Parameters:
 *
 * name - Name of the attribute whose value should be returned.
 * defaultValue - Optional default value to use if the attribute has no
 * value.
 */
mxCell.prototype.getAttribute = function (name, defaultValue) {
  var userObject = this.getValue();

  var val =
    userObject != null && userObject.nodeType == mxConstants.NODETYPE_ELEMENT ? userObject.getAttribute(name) : null;

  return val != null ? val : defaultValue;
};

/**
 * Function: setAttribute
 *
 * Sets the specified attribute on the user object if it is an XML node.
 *
 * Parameters:
 *
 * name - Name of the attribute whose value should be set.
 * value - New value of the attribute.
 */
mxCell.prototype.setAttribute = function (name, value) {
  var userObject = this.getValue();

  if (userObject != null && userObject.nodeType == mxConstants.NODETYPE_ELEMENT) {
    userObject.setAttribute(name, value);
  }
};

/**
 * Function: clone
 *
 * Returns a clone of the cell. Uses <cloneValue> to clone
 * the user object. All fields in <mxTransient> are ignored
 * during the cloning.
 */
mxCell.prototype.clone = function () {
  var clone = mxUtils.clone(this, this.mxTransient);
  clone.setValue(this.cloneValue());

  return clone;
};

/**
 * Function: cloneValue
 *
 * Returns a clone of the cell's user object.
 */
mxCell.prototype.cloneValue = function () {
  var value = this.getValue();

  if (value != null) {
    if (typeof value.clone == 'function') {
      value = value.clone();
    } else if (!isNaN(value.nodeType)) {
      value = value.cloneNode(true);
    }
  }

  return value;
};

/**
 * model/mxGeometry.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGeometry
 *
 * Extends <mxRectangle> to represent the geometry of a cell.
 *
 * For vertices, the geometry consists of the x- and y-location, and the width
 * and height. For edges, the geometry consists of the optional terminal- and
 * control points. The terminal points are only required if an edge is
 * unconnected, and are stored in the <sourcePoint> and <targetPoint>
 * variables, respectively.
 *
 * Example:
 *
 * If an edge is unconnected, that is, it has no source or target terminal,
 * then a geometry with terminal points for a new edge can be defined as
 * follows.
 *
 * (code)
 * geometry.setTerminalPoint(new mxPoint(x1, y1), true);
 * geometry.points = [new mxPoint(x2, y2)];
 * geometry.setTerminalPoint(new mxPoint(x3, y3), false);
 * (end)
 *
 * Control points are used regardless of the connected state of an edge and may
 * be ignored or interpreted differently depending on the edge's <mxEdgeStyle>.
 *
 * To disable automatic reset of control points after a cell has been moved or
 * resized, the the <mxGraph.resizeEdgesOnMove> and
 * <mxGraph.resetEdgesOnResize> may be used.
 *
 * Edge Labels:
 *
 * Using the x- and y-coordinates of a cell's geometry, it is possible to
 * position the label on edges on a specific location on the actual edge shape
 * as it appears on the screen. The x-coordinate of an edge's geometry is used
 * to describe the distance from the center of the edge from -1 to 1 with 0
 * being the center of the edge and the default value. The y-coordinate of an
 * edge's geometry is used to describe the absolute, orthogonal distance in
 * pixels from that point. In addition, the <mxGeometry.offset> is used as an
 * absolute offset vector from the resulting point.
 *
 * This coordinate system is applied if <relative> is true, otherwise the
 * offset defines the absolute vector from the edge's center point to the
 * label and the values for <x> and <y> are ignored.
 *
 * The width and height parameter for edge geometries can be used to set the
 * label width and height (eg. for word wrapping).
 *
 * Ports:
 *
 * The term "port" refers to a relatively positioned, connectable child cell,
 * which is used to specify the connection between the parent and another cell
 * in the graph. Ports are typically modeled as vertices with relative
 * geometries.
 *
 * Offsets:
 *
 * The <offset> field is interpreted in 3 different ways, depending on the cell
 * and the geometry. For edges, the offset defines the absolute offset for the
 * edge label. For relative geometries, the offset defines the absolute offset
 * for the origin (top, left corner) of the vertex, otherwise the offset
 * defines the absolute offset for the label inside the vertex or group.
 *
 * Constructor: mxGeometry
 *
 * Constructs a new object to describe the size and location of a vertex or
 * the control points of an edge.
 */
function mxGeometry(x, y, width, height) {
  mxRectangle.call(this, x, y, width, height);
}

/**
 * Extends mxRectangle.
 */
mxGeometry.prototype = new mxRectangle();
mxGeometry.prototype.constructor = mxGeometry;

/**
 * Variable: TRANSLATE_CONTROL_POINTS
 *
 * Global switch to translate the points in translate. Default is true.
 */
mxGeometry.prototype.TRANSLATE_CONTROL_POINTS = true;

/**
 * Variable: alternateBounds
 *
 * Stores alternate values for x, y, width and height in a rectangle. See
 * <swap> to exchange the values. Default is null.
 */
mxGeometry.prototype.alternateBounds = null;

/**
 * Variable: sourcePoint
 *
 * Defines the source <mxPoint> of the edge. This is used if the
 * corresponding edge does not have a source vertex. Otherwise it is
 * ignored. Default is  null.
 */
mxGeometry.prototype.sourcePoint = null;

/**
 * Variable: targetPoint
 *
 * Defines the target <mxPoint> of the edge. This is used if the
 * corresponding edge does not have a target vertex. Otherwise it is
 * ignored. Default is null.
 */
mxGeometry.prototype.targetPoint = null;

/**
 * Variable: points
 *
 * Array of <mxPoints> which specifies the control points along the edge.
 * These points are the intermediate points on the edge, for the endpoints
 * use <targetPoint> and <sourcePoint> or set the terminals of the edge to
 * a non-null value. Default is null.
 */
mxGeometry.prototype.points = null;

/**
 * Variable: offset
 *
 * For edges, this holds the offset (in pixels) from the position defined
 * by <x> and <y> on the edge. For relative geometries (for vertices), this
 * defines the absolute offset from the point defined by the relative
 * coordinates. For absolute geometries (for vertices), this defines the
 * offset for the label. Default is null.
 */
mxGeometry.prototype.offset = null;

/**
 * Variable: relative
 *
 * Specifies if the coordinates in the geometry are to be interpreted as
 * relative coordinates. For edges, this is used to define the location of
 * the edge label relative to the edge as rendered on the display. For
 * vertices, this specifies the relative location inside the bounds of the
 * parent cell.
 *
 * If this is false, then the coordinates are relative to the origin of the
 * parent cell or, for edges, the edge label position is relative to the
 * center of the edge as rendered on screen.
 *
 * Default is false.
 */
mxGeometry.prototype.relative = false;

/**
 * Function: swap
 *
 * Swaps the x, y, width and height with the values stored in
 * <alternateBounds> and puts the previous values into <alternateBounds> as
 * a rectangle. This operation is carried-out in-place, that is, using the
 * existing geometry instance. If this operation is called during a graph
 * model transactional change, then the geometry should be cloned before
 * calling this method and setting the geometry of the cell using
 * <mxGraphModel.setGeometry>.
 */
mxGeometry.prototype.swap = function () {
  if (this.alternateBounds != null) {
    var old = new mxRectangle(this.x, this.y, this.width, this.height);

    this.x = this.alternateBounds.x;
    this.y = this.alternateBounds.y;
    this.width = this.alternateBounds.width;
    this.height = this.alternateBounds.height;

    this.alternateBounds = old;
  }
};

/**
 * Function: getTerminalPoint
 *
 * Returns the <mxPoint> representing the source or target point of this
 * edge. This is only used if the edge has no source or target vertex.
 *
 * Parameters:
 *
 * isSource - Boolean that specifies if the source or target point
 * should be returned.
 */
mxGeometry.prototype.getTerminalPoint = function (isSource) {
  return isSource ? this.sourcePoint : this.targetPoint;
};

/**
 * Function: setTerminalPoint
 *
 * Sets the <sourcePoint> or <targetPoint> to the given <mxPoint> and
 * returns the new point.
 *
 * Parameters:
 *
 * point - Point to be used as the new source or target point.
 * isSource - Boolean that specifies if the source or target point
 * should be set.
 */
mxGeometry.prototype.setTerminalPoint = function (point, isSource) {
  if (isSource) {
    this.sourcePoint = point;
  } else {
    this.targetPoint = point;
  }

  return point;
};

/**
 * Function: rotate
 *
 * Rotates the geometry by the given angle around the given center. That is,
 * <x> and <y> of the geometry, the <sourcePoint>, <targetPoint> and all
 * <points> are translated by the given amount. <x> and <y> are only
 * translated if <relative> is false.
 *
 * Parameters:
 *
 * angle - Number that specifies the rotation angle in degrees.
 * cx - <mxPoint> that specifies the center of the rotation.
 */
mxGeometry.prototype.rotate = function (angle, cx) {
  var rad = mxUtils.toRadians(angle);
  var cos = Math.cos(rad);
  var sin = Math.sin(rad);

  // Rotates the geometry
  if (!this.relative) {
    var ct = new mxPoint(this.getCenterX(), this.getCenterY());
    var pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);

    this.x = Math.round(pt.x - this.width / 2);
    this.y = Math.round(pt.y - this.height / 2);
  }

  // Rotates the source point
  if (this.sourcePoint != null) {
    var pt = mxUtils.getRotatedPoint(this.sourcePoint, cos, sin, cx);
    this.sourcePoint.x = Math.round(pt.x);
    this.sourcePoint.y = Math.round(pt.y);
  }

  // Translates the target point
  if (this.targetPoint != null) {
    var pt = mxUtils.getRotatedPoint(this.targetPoint, cos, sin, cx);
    this.targetPoint.x = Math.round(pt.x);
    this.targetPoint.y = Math.round(pt.y);
  }

  // Translate the control points
  if (this.points != null) {
    for (var i = 0; i < this.points.length; i++) {
      if (this.points[i] != null) {
        var pt = mxUtils.getRotatedPoint(this.points[i], cos, sin, cx);
        this.points[i].x = Math.round(pt.x);
        this.points[i].y = Math.round(pt.y);
      }
    }
  }
};

/**
 * Function: translate
 *
 * Translates the geometry by the specified amount. That is, <x> and <y> of the
 * geometry, the <sourcePoint>, <targetPoint> and all <points> are translated
 * by the given amount. <x> and <y> are only translated if <relative> is false.
 * If <TRANSLATE_CONTROL_POINTS> is false, then <points> are not modified by
 * this function.
 *
 * Parameters:
 *
 * dx - Number that specifies the x-coordinate of the translation.
 * dy - Number that specifies the y-coordinate of the translation.
 */
mxGeometry.prototype.translate = function (dx, dy) {
  dx = parseFloat(dx);
  dy = parseFloat(dy);

  // Translates the geometry
  if (!this.relative) {
    this.x = parseFloat(this.x) + dx;
    this.y = parseFloat(this.y) + dy;
  }

  // Translates the source point
  if (this.sourcePoint != null) {
    this.sourcePoint.x = parseFloat(this.sourcePoint.x) + dx;
    this.sourcePoint.y = parseFloat(this.sourcePoint.y) + dy;
  }

  // Translates the target point
  if (this.targetPoint != null) {
    this.targetPoint.x = parseFloat(this.targetPoint.x) + dx;
    this.targetPoint.y = parseFloat(this.targetPoint.y) + dy;
  }

  // Translate the control points
  if (this.TRANSLATE_CONTROL_POINTS && this.points != null) {
    for (var i = 0; i < this.points.length; i++) {
      if (this.points[i] != null) {
        this.points[i].x = parseFloat(this.points[i].x) + dx;
        this.points[i].y = parseFloat(this.points[i].y) + dy;
      }
    }
  }
};

/**
 * Function: scale
 *
 * Scales the geometry by the given amount. That is, <x> and <y> of the
 * geometry, the <sourcePoint>, <targetPoint> and all <points> are scaled
 * by the given amount. <x>, <y>, <width> and <height> are only scaled if
 * <relative> is false. If <fixedAspect> is true, then the smaller value
 * is used to scale the width and the height.
 *
 * Parameters:
 *
 * sx - Number that specifies the horizontal scale factor.
 * sy - Number that specifies the vertical scale factor.
 * fixedAspect - Optional boolean to keep the aspect ratio fixed.
 */
mxGeometry.prototype.scale = function (sx, sy, fixedAspect) {
  sx = parseFloat(sx);
  sy = parseFloat(sy);

  // Translates the source point
  if (this.sourcePoint != null) {
    this.sourcePoint.x = parseFloat(this.sourcePoint.x) * sx;
    this.sourcePoint.y = parseFloat(this.sourcePoint.y) * sy;
  }

  // Translates the target point
  if (this.targetPoint != null) {
    this.targetPoint.x = parseFloat(this.targetPoint.x) * sx;
    this.targetPoint.y = parseFloat(this.targetPoint.y) * sy;
  }

  // Translate the control points
  if (this.points != null) {
    for (var i = 0; i < this.points.length; i++) {
      if (this.points[i] != null) {
        this.points[i].x = parseFloat(this.points[i].x) * sx;
        this.points[i].y = parseFloat(this.points[i].y) * sy;
      }
    }
  }

  // Translates the geometry
  if (!this.relative) {
    this.x = parseFloat(this.x) * sx;
    this.y = parseFloat(this.y) * sy;

    if (fixedAspect) {
      sy = sx = Math.min(sx, sy);
    }

    this.width = parseFloat(this.width) * sx;
    this.height = parseFloat(this.height) * sy;
  }
};

/**
 * Function: equals
 *
 * Returns true if the given object equals this geometry.
 */
mxGeometry.prototype.equals = function (obj) {
  return (
    mxRectangle.prototype.equals.apply(this, arguments) &&
    this.relative == obj.relative &&
    ((this.sourcePoint == null && obj.sourcePoint == null) ||
      (this.sourcePoint != null && this.sourcePoint.equals(obj.sourcePoint))) &&
    ((this.targetPoint == null && obj.targetPoint == null) ||
      (this.targetPoint != null && this.targetPoint.equals(obj.targetPoint))) &&
    ((this.points == null && obj.points == null) ||
      (this.points != null && mxUtils.equalPoints(this.points, obj.points))) &&
    ((this.alternateBounds == null && obj.alternateBounds == null) ||
      (this.alternateBounds != null && this.alternateBounds.equals(obj.alternateBounds))) &&
    ((this.offset == null && obj.offset == null) || (this.offset != null && this.offset.equals(obj.offset)))
  );
};

/**
 * model/mxCellPath.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxCellPath = {
  /**
   * Class: mxCellPath
   *
   * Implements a mechanism for temporary cell Ids.
   *
   * Variable: PATH_SEPARATOR
   *
   * Defines the separator between the path components. Default is ".".
   */
  PATH_SEPARATOR: '.',

  /**
   * Function: create
   *
   * Creates the cell path for the given cell. The cell path is a
   * concatenation of the indices of all ancestors on the (finite) path to
   * the root, eg. "0.0.0.1".
   *
   * Parameters:
   *
   * cell - Cell whose path should be returned.
   */
  create: function (cell) {
    var result = '';

    if (cell != null) {
      var parent = cell.getParent();

      while (parent != null) {
        var index = parent.getIndex(cell);
        result = index + mxCellPath.PATH_SEPARATOR + result;

        cell = parent;
        parent = cell.getParent();
      }
    }

    // Removes trailing separator
    var n = result.length;

    if (n > 1) {
      result = result.substring(0, n - 1);
    }

    return result;
  },

  /**
   * Function: getParentPath
   *
   * Returns the path for the parent of the cell represented by the given
   * path. Returns null if the given path has no parent.
   *
   * Parameters:
   *
   * path - Path whose parent path should be returned.
   */
  getParentPath: function (path) {
    if (path != null) {
      var index = path.lastIndexOf(mxCellPath.PATH_SEPARATOR);

      if (index >= 0) {
        return path.substring(0, index);
      } else if (path.length > 0) {
        return '';
      }
    }

    return null;
  },

  /**
   * Function: resolve
   *
   * Returns the cell for the specified cell path using the given root as the
   * root of the path.
   *
   * Parameters:
   *
   * root - Root cell of the path to be resolved.
   * path - String that defines the path.
   */
  resolve: function (root, path) {
    var parent = root;

    if (path != null) {
      var tokens = path.split(mxCellPath.PATH_SEPARATOR);

      for (var i = 0; i < tokens.length; i++) {
        parent = parent.getChildAt(parseInt(tokens[i]));
      }
    }

    return parent;
  },

  /**
   * Function: compare
   *
   * Compares the given cell paths and returns -1 if p1 is smaller, 0 if
   * p1 is equal and 1 if p1 is greater than p2.
   */
  compare: function (p1, p2) {
    var min = Math.min(p1.length, p2.length);
    var comp = 0;

    for (var i = 0; i < min; i++) {
      if (p1[i] != p2[i]) {
        if (p1[i].length == 0 || p2[i].length == 0) {
          comp = p1[i] == p2[i] ? 0 : p1[i] > p2[i] ? 1 : -1;
        } else {
          var t1 = parseInt(p1[i]);
          var t2 = parseInt(p2[i]);

          comp = t1 == t2 ? 0 : t1 > t2 ? 1 : -1;
        }

        break;
      }
    }

    // Compares path length if both paths are equal to this point
    if (comp == 0) {
      var t1 = p1.length;
      var t2 = p2.length;

      if (t1 != t2) {
        comp = t1 > t2 ? 1 : -1;
      }
    }

    return comp;
  },
};

/**
 * view/mxPerimeter.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxPerimeter = {
  /**
   * Class: mxPerimeter
   *
   * Provides various perimeter functions to be used in a style
   * as the value of <mxConstants.STYLE_PERIMETER>. Perimeters for
   * rectangle, circle, rhombus and triangle are available.
   *
   * Example:
   *
   * (code)
   * <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
   * (end)
   *
   * Or programmatically:
   *
   * (code)
   * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
   * (end)
   *
   * When adding new perimeter functions, it is recommended to use the
   * mxPerimeter-namespace as follows:
   *
   * (code)
   * mxPerimeter.CustomPerimeter = function (bounds, vertex, next, orthogonal)
   * {
   *   var x = 0; // Calculate x-coordinate
   *   var y = 0; // Calculate y-coordainte
   *
   *   return new mxPoint(x, y);
   * }
   * (end)
   *
   * The new perimeter should then be registered in the <mxStyleRegistry> as follows:
   * (code)
   * mxStyleRegistry.putValue('customPerimeter', mxPerimeter.CustomPerimeter);
   * (end)
   *
   * The custom perimeter above can now be used in a specific vertex as follows:
   *
   * (code)
   * model.setStyle(vertex, 'perimeter=customPerimeter');
   * (end)
   *
   * Note that the key of the <mxStyleRegistry> entry for the function should
   * be used in string values, unless <mxGraphView.allowEval> is true, in
   * which case you can also use mxPerimeter.CustomPerimeter for the value in
   * the cell style above.
   *
   * Or it can be used for all vertices in the graph as follows:
   *
   * (code)
   * var style = graph.getStylesheet().getDefaultVertexStyle();
   * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.CustomPerimeter;
   * (end)
   *
   * Note that the object can be used directly when programmatically setting
   * the value, but the key in the <mxStyleRegistry> should be used when
   * setting the value via a key, value pair in a cell style.
   *
   * The parameters are explained in <RectanglePerimeter>.
   *
   * Function: RectanglePerimeter
   *
   * Describes a rectangular perimeter for the given bounds.
   *
   * Parameters:
   *
   * bounds - <mxRectangle> that represents the absolute bounds of the
   * vertex.
   * vertex - <mxCellState> that represents the vertex.
   * next - <mxPoint> that represents the nearest neighbour point on the
   * given edge.
   * orthogonal - Boolean that specifies if the orthogonal projection onto
   * the perimeter should be returned. If this is false then the intersection
   * of the perimeter and the line between the next and the center point is
   * returned.
   */
  RectanglePerimeter: function (bounds, vertex, next, orthogonal) {
    var cx = bounds.getCenterX();
    var cy = bounds.getCenterY();
    var dx = next.x - cx;
    var dy = next.y - cy;
    var alpha = Math.atan2(dy, dx);
    var p = new mxPoint(0, 0);
    var pi = Math.PI;
    var pi2 = Math.PI / 2;
    var beta = pi2 - alpha;
    var t = Math.atan2(bounds.height, bounds.width);

    if (alpha < -pi + t || alpha > pi - t) {
      // Left edge
      p.x = bounds.x;
      p.y = cy - (bounds.width * Math.tan(alpha)) / 2;
    } else if (alpha < -t) {
      // Top Edge
      p.y = bounds.y;
      p.x = cx - (bounds.height * Math.tan(beta)) / 2;
    } else if (alpha < t) {
      // Right Edge
      p.x = bounds.x + bounds.width;
      p.y = cy + (bounds.width * Math.tan(alpha)) / 2;
    } else {
      // Bottom Edge
      p.y = bounds.y + bounds.height;
      p.x = cx + (bounds.height * Math.tan(beta)) / 2;
    }

    if (orthogonal) {
      if (next.x >= bounds.x && next.x <= bounds.x + bounds.width) {
        p.x = next.x;
      } else if (next.y >= bounds.y && next.y <= bounds.y + bounds.height) {
        p.y = next.y;
      }
      if (next.x < bounds.x) {
        p.x = bounds.x;
      } else if (next.x > bounds.x + bounds.width) {
        p.x = bounds.x + bounds.width;
      }
      if (next.y < bounds.y) {
        p.y = bounds.y;
      } else if (next.y > bounds.y + bounds.height) {
        p.y = bounds.y + bounds.height;
      }
    }

    return p;
  },

  /**
   * Function: EllipsePerimeter
   *
   * Describes an elliptic perimeter. See <RectanglePerimeter>
   * for a description of the parameters.
   */
  EllipsePerimeter: function (bounds, vertex, next, orthogonal) {
    var x = bounds.x;
    var y = bounds.y;
    var a = bounds.width / 2;
    var b = bounds.height / 2;
    var cx = x + a;
    var cy = y + b;
    var px = next.x;
    var py = next.y;

    // Calculates straight line equation through
    // point and ellipse center y = d * x + h
    var dx = parseInt(px - cx);
    var dy = parseInt(py - cy);

    if (dx == 0 && dy != 0) {
      return new mxPoint(cx, cy + (b * dy) / Math.abs(dy));
    } else if (dx == 0 && dy == 0) {
      return new mxPoint(px, py);
    }

    if (orthogonal) {
      if (py >= y && py <= y + bounds.height) {
        var ty = py - cy;
        var tx = Math.sqrt(a * a * (1 - (ty * ty) / (b * b))) || 0;

        if (px <= x) {
          tx = -tx;
        }

        return new mxPoint(cx + tx, py);
      }

      if (px >= x && px <= x + bounds.width) {
        var tx = px - cx;
        var ty = Math.sqrt(b * b * (1 - (tx * tx) / (a * a))) || 0;

        if (py <= y) {
          ty = -ty;
        }

        return new mxPoint(px, cy + ty);
      }
    }

    // Calculates intersection
    var d = dy / dx;
    var h = cy - d * cx;
    var e = a * a * d * d + b * b;
    var f = -2 * cx * e;
    var g = a * a * d * d * cx * cx + b * b * cx * cx - a * a * b * b;
    var det = Math.sqrt(f * f - 4 * e * g);

    // Two solutions (perimeter points)
    var xout1 = (-f + det) / (2 * e);
    var xout2 = (-f - det) / (2 * e);
    var yout1 = d * xout1 + h;
    var yout2 = d * xout2 + h;
    var dist1 = Math.sqrt(Math.pow(xout1 - px, 2) + Math.pow(yout1 - py, 2));
    var dist2 = Math.sqrt(Math.pow(xout2 - px, 2) + Math.pow(yout2 - py, 2));

    // Correct solution
    var xout = 0;
    var yout = 0;

    if (dist1 < dist2) {
      xout = xout1;
      yout = yout1;
    } else {
      xout = xout2;
      yout = yout2;
    }

    return new mxPoint(xout, yout);
  },

  /**
   * Function: RhombusPerimeter
   *
   * Describes a rhombus (aka diamond) perimeter. See <RectanglePerimeter>
   * for a description of the parameters.
   */
  RhombusPerimeter: function (bounds, vertex, next, orthogonal) {
    var x = bounds.x;
    var y = bounds.y;
    var w = bounds.width;
    var h = bounds.height;

    var cx = x + w / 2;
    var cy = y + h / 2;

    var px = next.x;
    var py = next.y;

    // Special case for intersecting the diamond's corners
    if (cx == px) {
      if (cy > py) {
        return new mxPoint(cx, y); // top
      } else {
        return new mxPoint(cx, y + h); // bottom
      }
    } else if (cy == py) {
      if (cx > px) {
        return new mxPoint(x, cy); // left
      } else {
        return new mxPoint(x + w, cy); // right
      }
    }

    var tx = cx;
    var ty = cy;

    if (orthogonal) {
      if (px >= x && px <= x + w) {
        tx = px;
      } else if (py >= y && py <= y + h) {
        ty = py;
      }
    }

    // In which quadrant will the intersection be?
    // set the slope and offset of the border line accordingly
    if (px < cx) {
      if (py < cy) {
        return mxUtils.intersection(px, py, tx, ty, cx, y, x, cy);
      } else {
        return mxUtils.intersection(px, py, tx, ty, cx, y + h, x, cy);
      }
    } else if (py < cy) {
      return mxUtils.intersection(px, py, tx, ty, cx, y, x + w, cy);
    } else {
      return mxUtils.intersection(px, py, tx, ty, cx, y + h, x + w, cy);
    }
  },

  /**
   * Function: TrianglePerimeter
   *
   * Describes a triangle perimeter. See <RectanglePerimeter>
   * for a description of the parameters.
   */
  TrianglePerimeter: function (bounds, vertex, next, orthogonal) {
    var direction = vertex != null ? vertex.style[mxConstants.STYLE_DIRECTION] : null;
    var vertical = direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH;

    var x = bounds.x;
    var y = bounds.y;
    var w = bounds.width;
    var h = bounds.height;

    var cx = x + w / 2;
    var cy = y + h / 2;

    var start = new mxPoint(x, y);
    var corner = new mxPoint(x + w, cy);
    var end = new mxPoint(x, y + h);

    if (direction == mxConstants.DIRECTION_NORTH) {
      start = end;
      corner = new mxPoint(cx, y);
      end = new mxPoint(x + w, y + h);
    } else if (direction == mxConstants.DIRECTION_SOUTH) {
      corner = new mxPoint(cx, y + h);
      end = new mxPoint(x + w, y);
    } else if (direction == mxConstants.DIRECTION_WEST) {
      start = new mxPoint(x + w, y);
      corner = new mxPoint(x, cy);
      end = new mxPoint(x + w, y + h);
    }

    var dx = next.x - cx;
    var dy = next.y - cy;

    var alpha = vertical ? Math.atan2(dx, dy) : Math.atan2(dy, dx);
    var t = vertical ? Math.atan2(w, h) : Math.atan2(h, w);

    var base = false;

    if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_WEST) {
      base = alpha > -t && alpha < t;
    } else {
      base = alpha < -Math.PI + t || alpha > Math.PI - t;
    }

    var result = null;

    if (base) {
      if (
        orthogonal &&
        ((vertical && next.x >= start.x && next.x <= end.x) || (!vertical && next.y >= start.y && next.y <= end.y))
      ) {
        if (vertical) {
          result = new mxPoint(next.x, start.y);
        } else {
          result = new mxPoint(start.x, next.y);
        }
      } else {
        if (direction == mxConstants.DIRECTION_NORTH) {
          result = new mxPoint(x + w / 2 + (h * Math.tan(alpha)) / 2, y + h);
        } else if (direction == mxConstants.DIRECTION_SOUTH) {
          result = new mxPoint(x + w / 2 - (h * Math.tan(alpha)) / 2, y);
        } else if (direction == mxConstants.DIRECTION_WEST) {
          result = new mxPoint(x + w, y + h / 2 + (w * Math.tan(alpha)) / 2);
        } else {
          result = new mxPoint(x, y + h / 2 - (w * Math.tan(alpha)) / 2);
        }
      }
    } else {
      if (orthogonal) {
        var pt = new mxPoint(cx, cy);

        if (next.y >= y && next.y <= y + h) {
          pt.x = vertical ? cx : direction == mxConstants.DIRECTION_WEST ? x + w : x;
          pt.y = next.y;
        } else if (next.x >= x && next.x <= x + w) {
          pt.x = next.x;
          pt.y = !vertical ? cy : direction == mxConstants.DIRECTION_NORTH ? y + h : y;
        }

        // Compute angle
        dx = next.x - pt.x;
        dy = next.y - pt.y;

        cx = pt.x;
        cy = pt.y;
      }

      if ((vertical && next.x <= x + w / 2) || (!vertical && next.y <= y + h / 2)) {
        result = mxUtils.intersection(next.x, next.y, cx, cy, start.x, start.y, corner.x, corner.y);
      } else {
        result = mxUtils.intersection(next.x, next.y, cx, cy, corner.x, corner.y, end.x, end.y);
      }
    }

    if (result == null) {
      result = new mxPoint(cx, cy);
    }

    return result;
  },

  /**
   * Function: HexagonPerimeter
   *
   * Describes a hexagon perimeter. See <RectanglePerimeter>
   * for a description of the parameters.
   */
  HexagonPerimeter: function (bounds, vertex, next, orthogonal) {
    var x = bounds.x;
    var y = bounds.y;
    var w = bounds.width;
    var h = bounds.height;

    var cx = bounds.getCenterX();
    var cy = bounds.getCenterY();
    var px = next.x;
    var py = next.y;
    var dx = px - cx;
    var dy = py - cy;
    var alpha = -Math.atan2(dy, dx);
    var pi = Math.PI;
    var pi2 = Math.PI / 2;

    var result = new mxPoint(cx, cy);

    var direction =
      vertex != null
        ? mxUtils.getValue(vertex.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST)
        : mxConstants.DIRECTION_EAST;
    var vertical = direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH;
    var a = new mxPoint();
    var b = new mxPoint();

    //Only consider corrects quadrants for the orthogonal case.
    if ((px < x && py < y) || (px < x && py > y + h) || (px > x + w && py < y) || (px > x + w && py > y + h)) {
      orthogonal = false;
    }

    if (orthogonal) {
      if (vertical) {
        //Special cases where intersects with hexagon corners
        if (px == cx) {
          if (py <= y) {
            return new mxPoint(cx, y);
          } else if (py >= y + h) {
            return new mxPoint(cx, y + h);
          }
        } else if (px < x) {
          if (py == y + h / 4) {
            return new mxPoint(x, y + h / 4);
          } else if (py == y + (3 * h) / 4) {
            return new mxPoint(x, y + (3 * h) / 4);
          }
        } else if (px > x + w) {
          if (py == y + h / 4) {
            return new mxPoint(x + w, y + h / 4);
          } else if (py == y + (3 * h) / 4) {
            return new mxPoint(x + w, y + (3 * h) / 4);
          }
        } else if (px == x) {
          if (py < cy) {
            return new mxPoint(x, y + h / 4);
          } else if (py > cy) {
            return new mxPoint(x, y + (3 * h) / 4);
          }
        } else if (px == x + w) {
          if (py < cy) {
            return new mxPoint(x + w, y + h / 4);
          } else if (py > cy) {
            return new mxPoint(x + w, y + (3 * h) / 4);
          }
        }
        if (py == y) {
          return new mxPoint(cx, y);
        } else if (py == y + h) {
          return new mxPoint(cx, y + h);
        }

        if (px < cx) {
          if (py > y + h / 4 && py < y + (3 * h) / 4) {
            a = new mxPoint(x, y);
            b = new mxPoint(x, y + h);
          } else if (py < y + h / 4) {
            a = new mxPoint(x - Math.floor(0.5 * w), y + Math.floor(0.5 * h));
            b = new mxPoint(x + w, y - Math.floor(0.25 * h));
          } else if (py > y + (3 * h) / 4) {
            a = new mxPoint(x - Math.floor(0.5 * w), y + Math.floor(0.5 * h));
            b = new mxPoint(x + w, y + Math.floor(1.25 * h));
          }
        } else if (px > cx) {
          if (py > y + h / 4 && py < y + (3 * h) / 4) {
            a = new mxPoint(x + w, y);
            b = new mxPoint(x + w, y + h);
          } else if (py < y + h / 4) {
            a = new mxPoint(x, y - Math.floor(0.25 * h));
            b = new mxPoint(x + Math.floor(1.5 * w), y + Math.floor(0.5 * h));
          } else if (py > y + (3 * h) / 4) {
            a = new mxPoint(x + Math.floor(1.5 * w), y + Math.floor(0.5 * h));
            b = new mxPoint(x, y + Math.floor(1.25 * h));
          }
        }
      } else {
        //Special cases where intersects with hexagon corners
        if (py == cy) {
          if (px <= x) {
            return new mxPoint(x, y + h / 2);
          } else if (px >= x + w) {
            return new mxPoint(x + w, y + h / 2);
          }
        } else if (py < y) {
          if (px == x + w / 4) {
            return new mxPoint(x + w / 4, y);
          } else if (px == x + (3 * w) / 4) {
            return new mxPoint(x + (3 * w) / 4, y);
          }
        } else if (py > y + h) {
          if (px == x + w / 4) {
            return new mxPoint(x + w / 4, y + h);
          } else if (px == x + (3 * w) / 4) {
            return new mxPoint(x + (3 * w) / 4, y + h);
          }
        } else if (py == y) {
          if (px < cx) {
            return new mxPoint(x + w / 4, y);
          } else if (px > cx) {
            return new mxPoint(x + (3 * w) / 4, y);
          }
        } else if (py == y + h) {
          if (px < cx) {
            return new mxPoint(x + w / 4, y + h);
          } else if (py > cy) {
            return new mxPoint(x + (3 * w) / 4, y + h);
          }
        }
        if (px == x) {
          return new mxPoint(x, cy);
        } else if (px == x + w) {
          return new mxPoint(x + w, cy);
        }

        if (py < cy) {
          if (px > x + w / 4 && px < x + (3 * w) / 4) {
            a = new mxPoint(x, y);
            b = new mxPoint(x + w, y);
          } else if (px < x + w / 4) {
            a = new mxPoint(x - Math.floor(0.25 * w), y + h);
            b = new mxPoint(x + Math.floor(0.5 * w), y - Math.floor(0.5 * h));
          } else if (px > x + (3 * w) / 4) {
            a = new mxPoint(x + Math.floor(0.5 * w), y - Math.floor(0.5 * h));
            b = new mxPoint(x + Math.floor(1.25 * w), y + h);
          }
        } else if (py > cy) {
          if (px > x + w / 4 && px < x + (3 * w) / 4) {
            a = new mxPoint(x, y + h);
            b = new mxPoint(x + w, y + h);
          } else if (px < x + w / 4) {
            a = new mxPoint(x - Math.floor(0.25 * w), y);
            b = new mxPoint(x + Math.floor(0.5 * w), y + Math.floor(1.5 * h));
          } else if (px > x + (3 * w) / 4) {
            a = new mxPoint(x + Math.floor(0.5 * w), y + Math.floor(1.5 * h));
            b = new mxPoint(x + Math.floor(1.25 * w), y);
          }
        }
      }

      var tx = cx;
      var ty = cy;

      if (px >= x && px <= x + w) {
        tx = px;

        if (py < cy) {
          ty = y + h;
        } else {
          ty = y;
        }
      } else if (py >= y && py <= y + h) {
        ty = py;

        if (px < cx) {
          tx = x + w;
        } else {
          tx = x;
        }
      }

      result = mxUtils.intersection(tx, ty, next.x, next.y, a.x, a.y, b.x, b.y);
    } else {
      if (vertical) {
        var beta = Math.atan2(h / 4, w / 2);

        //Special cases where intersects with hexagon corners
        if (alpha == beta) {
          return new mxPoint(x + w, y + Math.floor(0.25 * h));
        } else if (alpha == pi2) {
          return new mxPoint(x + Math.floor(0.5 * w), y);
        } else if (alpha == pi - beta) {
          return new mxPoint(x, y + Math.floor(0.25 * h));
        } else if (alpha == -beta) {
          return new mxPoint(x + w, y + Math.floor(0.75 * h));
        } else if (alpha == -pi2) {
          return new mxPoint(x + Math.floor(0.5 * w), y + h);
        } else if (alpha == -pi + beta) {
          return new mxPoint(x, y + Math.floor(0.75 * h));
        }

        if (alpha < beta && alpha > -beta) {
          a = new mxPoint(x + w, y);
          b = new mxPoint(x + w, y + h);
        } else if (alpha > beta && alpha < pi2) {
          a = new mxPoint(x, y - Math.floor(0.25 * h));
          b = new mxPoint(x + Math.floor(1.5 * w), y + Math.floor(0.5 * h));
        } else if (alpha > pi2 && alpha < pi - beta) {
          a = new mxPoint(x - Math.floor(0.5 * w), y + Math.floor(0.5 * h));
          b = new mxPoint(x + w, y - Math.floor(0.25 * h));
        } else if ((alpha > pi - beta && alpha <= pi) || (alpha < -pi + beta && alpha >= -pi)) {
          a = new mxPoint(x, y);
          b = new mxPoint(x, y + h);
        } else if (alpha < -beta && alpha > -pi2) {
          a = new mxPoint(x + Math.floor(1.5 * w), y + Math.floor(0.5 * h));
          b = new mxPoint(x, y + Math.floor(1.25 * h));
        } else if (alpha < -pi2 && alpha > -pi + beta) {
          a = new mxPoint(x - Math.floor(0.5 * w), y + Math.floor(0.5 * h));
          b = new mxPoint(x + w, y + Math.floor(1.25 * h));
        }
      } else {
        var beta = Math.atan2(h / 2, w / 4);

        //Special cases where intersects with hexagon corners
        if (alpha == beta) {
          return new mxPoint(x + Math.floor(0.75 * w), y);
        } else if (alpha == pi - beta) {
          return new mxPoint(x + Math.floor(0.25 * w), y);
        } else if (alpha == pi || alpha == -pi) {
          return new mxPoint(x, y + Math.floor(0.5 * h));
        } else if (alpha == 0) {
          return new mxPoint(x + w, y + Math.floor(0.5 * h));
        } else if (alpha == -beta) {
          return new mxPoint(x + Math.floor(0.75 * w), y + h);
        } else if (alpha == -pi + beta) {
          return new mxPoint(x + Math.floor(0.25 * w), y + h);
        }

        if (alpha > 0 && alpha < beta) {
          a = new mxPoint(x + Math.floor(0.5 * w), y - Math.floor(0.5 * h));
          b = new mxPoint(x + Math.floor(1.25 * w), y + h);
        } else if (alpha > beta && alpha < pi - beta) {
          a = new mxPoint(x, y);
          b = new mxPoint(x + w, y);
        } else if (alpha > pi - beta && alpha < pi) {
          a = new mxPoint(x - Math.floor(0.25 * w), y + h);
          b = new mxPoint(x + Math.floor(0.5 * w), y - Math.floor(0.5 * h));
        } else if (alpha < 0 && alpha > -beta) {
          a = new mxPoint(x + Math.floor(0.5 * w), y + Math.floor(1.5 * h));
          b = new mxPoint(x + Math.floor(1.25 * w), y);
        } else if (alpha < -beta && alpha > -pi + beta) {
          a = new mxPoint(x, y + h);
          b = new mxPoint(x + w, y + h);
        } else if (alpha < -pi + beta && alpha > -pi) {
          a = new mxPoint(x - Math.floor(0.25 * w), y);
          b = new mxPoint(x + Math.floor(0.5 * w), y + Math.floor(1.5 * h));
        }
      }

      result = mxUtils.intersection(cx, cy, next.x, next.y, a.x, a.y, b.x, b.y);
    }

    if (result == null) {
      return new mxPoint(cx, cy);
    }

    return result;
  },
};

/**
 * view/mxPrintPreview.js
 */
/**
 * Copyright (c) 2006-2019, JGraph Ltd
 * Copyright (c) 2006-2017, draw.io AG
 */
/**
 * Class: mxPrintPreview
 *
 * Implements printing of a diagram across multiple pages. The following opens
 * a print preview for an existing graph:
 *
 * (code)
 * var preview = new mxPrintPreview(graph);
 * preview.open();
 * (end)
 *
 * Use <mxUtils.getScaleForPageCount> as follows in order to print the graph
 * across a given number of pages:
 *
 * (code)
 * var pageCount = mxUtils.prompt('Enter page count', '1');
 *
 * if (pageCount != null)
 * {
 *   var scale = mxUtils.getScaleForPageCount(pageCount, graph);
 *   var preview = new mxPrintPreview(graph, scale);
 *   preview.open();
 * }
 * (end)
 *
 * Additional pages:
 *
 * To add additional pages before and after the output, <getCoverPages> and
 * <getAppendices> can be used, respectively.
 *
 * (code)
 * var preview = new mxPrintPreview(graph, 1);
 *
 * preview.getCoverPages = function(w, h)
 * {
 *   return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)
 *   {
 *     div.innerHTML = '<div style="position:relative;margin:4px;">Cover Page</p>'
 *   }))];
 * };
 *
 * preview.getAppendices = function(w, h)
 * {
 *   return [this.renderPage(w, h, 0, 0, mxUtils.bind(this, function(div)
 *   {
 *     div.innerHTML = '<div style="position:relative;margin:4px;">Appendix</p>'
 *   }))];
 * };
 *
 * preview.open();
 * (end)
 *
 * CSS:
 *
 * The CSS from the original page is not carried over to the print preview.
 * To add CSS to the page, use the css argument in the <open> function or
 * override <writeHead> to add the respective link tags as follows:
 *
 * (code)
 * var writeHead = preview.writeHead;
 * preview.writeHead = function(doc, css)
 * {
 *   writeHead.apply(this, arguments);
 *   doc.writeln('<link rel="stylesheet" type="text/css" href="style.css">');
 * };
 * (end)
 *
 * Padding:
 *
 * To add a padding to the page in the preview (but not the print output), use
 * the following code:
 *
 * (code)
 * preview.writeHead = function(doc)
 * {
 *   writeHead.apply(this, arguments);
 *
 *   doc.writeln('<style type="text/css">');
 *   doc.writeln('@media screen {');
 *   doc.writeln('  body > div { padding-top:30px;padding-left:40px;box-sizing:content-box; }');
 *   doc.writeln('}');
 *   doc.writeln('</style>');
 * };
 * (end)
 *
 * Headers:
 *
 * Apart from setting the title argument in the mxPrintPreview constructor you
 * can override <renderPage> as follows to add a header to any page:
 *
 * (code)
 * var oldRenderPage = mxPrintPreview.prototype.renderPage;
 * mxPrintPreview.prototype.renderPage = function(w, h, x, y, content, pageNumber)
 * {
 *   var div = oldRenderPage.apply(this, arguments);
 *
 *   var header = document.createElement('div');
 *   header.style.position = 'absolute';
 *   header.style.top = '0px';
 *   header.style.width = '100%';
 *   header.style.textAlign = 'right';
 *   mxUtils.write(header, 'Your header here');
 *   div.firstChild.appendChild(header);
 *
 *   return div;
 * };
 * (end)
 *
 * The pageNumber argument contains the number of the current page, starting at
 * 1. To display a header on the first page only, check pageNumber and add a
 * vertical offset in the constructor call for the height of the header.
 *
 * Page Format:
 *
 * For landscape printing, use <mxConstants.PAGE_FORMAT_A4_LANDSCAPE> as
 * the pageFormat in <mxUtils.getScaleForPageCount> and <mxPrintPreview>.
 * Keep in mind that one can not set the defaults for the print dialog
 * of the operating system from JavaScript so the user must manually choose
 * a page format that matches this setting.
 *
 * You can try passing the following CSS directive to <open> to set the
 * page format in the print dialog to landscape. However, this CSS
 * directive seems to be ignored in most major browsers, including IE.
 *
 * (code)
 * @page {
 *   size: landscape;
 * }
 * (end)
 *
 * Note that the print preview behaves differently in IE when used from the
 * filesystem or via HTTP so printing should always be tested via HTTP.
 *
 * If you are using a DOCTYPE in the source page you can override <getDoctype>
 * and provide the same DOCTYPE for the print preview if required. Here is
 * an example for IE8 standards mode.
 *
 * (code)
 * var preview = new mxPrintPreview(graph);
 * preview.getDoctype = function()
 * {
 *   return '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=8" ><![endif]-->';
 * };
 * preview.open();
 * (end)
 *
 * Constructor: mxPrintPreview
 *
 * Constructs a new print preview for the given parameters.
 *
 * Parameters:
 *
 * graph - <mxGraph> to be previewed.
 * scale - Optional scale of the output. Default is 1 / <mxGraph.pageScale>.
 * pageFormat - <mxRectangle> that specifies the page format (in pixels).
 * border - Border in pixels along each side of every page. Note that the
 * actual print function in the browser will add another border for
 * printing.
 * This should match the page format of the printer. Default uses the
 * <mxGraph.pageFormat> of the given graph.
 * x0 - Optional left offset of the output. Default is 0.
 * y0 - Optional top offset of the output. Default is 0.
 * borderColor - Optional color of the page border. Default is no border.
 * Note that a border is sometimes useful to highlight the printed page
 * border in the print preview of the browser.
 * title - Optional string that is used for the window title. Default
 * is 'Printer-friendly version'.
 * pageSelector - Optional boolean that specifies if the page selector
 * should appear in the window with the print preview. Default is true.
 */
function mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector) {
  this.graph = graph;
  this.scale = scale != null ? scale : 1 / graph.pageScale;
  this.border = border != null ? border : 0;
  this.pageFormat = mxRectangle.fromRectangle(pageFormat != null ? pageFormat : graph.pageFormat);
  this.title = title != null ? title : 'Printer-friendly version';
  this.x0 = x0 != null ? x0 : 0;
  this.y0 = y0 != null ? y0 : 0;
  this.borderColor = borderColor;
  this.pageSelector = pageSelector != null ? pageSelector : true;
}

/**
 * Variable: graph
 *
 * Reference to the <mxGraph> that should be previewed.
 */
mxPrintPreview.prototype.graph = null;

/**
 * Variable: pageFormat
 *
 * Holds the <mxRectangle> that defines the page format.
 */
mxPrintPreview.prototype.pageFormat = null;

/**
 * Variable: scale
 *
 * Holds the scale of the print preview.
 */
mxPrintPreview.prototype.scale = null;

/**
 * Variable: border
 *
 * The border inset around each side of every page in the preview. This is set
 * to 0 if autoOrigin is false.
 */
mxPrintPreview.prototype.border = 0;

/**
 * Variable: marginTop
 *
 * The margin at the top of the page (number). Default is 0.
 */
mxPrintPreview.prototype.marginTop = 0;

/**
 * Variable: marginBottom
 *
 * The margin at the bottom of the page (number). Default is 0.
 */
mxPrintPreview.prototype.marginBottom = 0;

/**
 * Variable: x0
 *
 * Holds the horizontal offset of the output.
 */
mxPrintPreview.prototype.x0 = 0;

/**
 * Variable: y0
 *
 * Holds the vertical offset of the output.
 */
mxPrintPreview.prototype.y0 = 0;

/**
 * Variable: autoOrigin
 *
 * Specifies if the origin should be automatically computed based on the top,
 * left corner of the actual diagram contents. The required offset will be added
 * to <x0> and <y0> in <open>. Default is true.
 */
mxPrintPreview.prototype.autoOrigin = true;

/**
 * Variable: printOverlays
 *
 * Specifies if overlays should be printed. Default is false.
 */
mxPrintPreview.prototype.printOverlays = false;

/**
 * Variable: printControls
 *
 * Specifies if controls (such as folding icons) should be printed. Default is
 * false.
 */
mxPrintPreview.prototype.printControls = false;

/**
 * Variable: printBackgroundImage
 *
 * Specifies if the background image should be printed. Default is false.
 */
mxPrintPreview.prototype.printBackgroundImage = false;

/**
 * Variable: backgroundColor
 *
 * Holds the color value for the page background color. Default is #ffffff.
 */
mxPrintPreview.prototype.backgroundColor = '#ffffff';

/**
 * Variable: borderColor
 *
 * Holds the color value for the page border.
 */
mxPrintPreview.prototype.borderColor = null;

/**
 * Variable: title
 *
 * Holds the title of the preview window.
 */
mxPrintPreview.prototype.title = null;

/**
 * Variable: pageSelector
 *
 * Boolean that specifies if the page selector should be
 * displayed. Default is true.
 */
mxPrintPreview.prototype.pageSelector = null;

/**
 * Variable: wnd
 *
 * Reference to the preview window.
 */
mxPrintPreview.prototype.wnd = null;

/**
 * Variable: targetWindow
 *
 * Assign any window here to redirect the rendering in <open>.
 */
mxPrintPreview.prototype.targetWindow = null;

/**
 * Variable: pageCount
 *
 * Holds the actual number of pages in the preview.
 */
mxPrintPreview.prototype.pageCount = 0;

/**
 * Variable: clipping
 *
 * Specifies is clipping should be used to avoid creating too many cell states
 * in large diagrams. The bounding box of the cells in the original diagram is
 * used if this is enabled. Default is true.
 */
mxPrintPreview.prototype.clipping = true;

/**
 * Function: getWindow
 *
 * Returns <wnd>.
 */
mxPrintPreview.prototype.getWindow = function () {
  return this.wnd;
};

/**
 * Function: getDocType
 *
 * Returns the string that should go before the HTML tag in the print preview
 * page. This implementation returns an X-UA meta tag for IE5 in quirks mode,
 * IE8 in IE8 standards mode and edge in IE9 standards mode.
 */
mxPrintPreview.prototype.getDoctype = function () {
  var dt = '';

  if (document.documentMode == 5) {
    dt = '<meta http-equiv="X-UA-Compatible" content="IE=5">';
  } else if (document.documentMode == 8) {
    dt = '<meta http-equiv="X-UA-Compatible" content="IE=8">';
  } else if (document.documentMode > 8) {
    // Comment needed to make standards doctype apply in IE
    dt = '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->';
  }

  return dt;
};

/**
 * Function: appendGraph
 *
 * Adds the given graph to the existing print preview.
 *
 * Parameters:
 *
 * css - Optional CSS string to be used in the head section.
 * targetWindow - Optional window that should be used for rendering. If
 * this is specified then no HEAD tag, CSS and BODY tag will be written.
 */
mxPrintPreview.prototype.appendGraph = function (graph, scale, x0, y0, forcePageBreaks, keepOpen) {
  this.graph = graph;
  this.scale = scale != null ? scale : 1 / graph.pageScale;
  this.x0 = x0;
  this.y0 = y0;
  this.open(null, null, forcePageBreaks, keepOpen);
};

/**
 * Function: open
 *
 * Shows the print preview window. The window is created here if it does
 * not exist.
 *
 * Parameters:
 *
 * css - Optional CSS string to be used in the head section.
 * targetWindow - Optional window that should be used for rendering. If
 * this is specified then no HEAD tag, CSS and BODY tag will be written.
 */
mxPrintPreview.prototype.open = function (css, targetWindow, forcePageBreaks, keepOpen) {
  // Closing the window while the page is being rendered may cause an
  // exception in IE. This and any other exceptions are simply ignored.
  var previousInitializeOverlay = this.graph.cellRenderer.initializeOverlay;
  var div = null;

  try {
    // Temporarily overrides the method to redirect rendering of overlays
    // to the draw pane so that they are visible in the printout
    if (this.printOverlays) {
      this.graph.cellRenderer.initializeOverlay = function (state, overlay) {
        overlay.init(state.view.getDrawPane());
      };
    }

    if (this.printControls) {
      this.graph.cellRenderer.initControl = function (state, control, handleEvents, clickHandler) {
        control.dialect = state.view.graph.dialect;
        control.init(state.view.getDrawPane());
      };
    }

    this.wnd = targetWindow != null ? targetWindow : this.wnd;
    var isNewWindow = false;

    if (this.wnd == null) {
      isNewWindow = true;
      this.wnd = window.open();
    }

    var doc = this.wnd.document;

    if (isNewWindow) {
      var dt = this.getDoctype();

      if (dt != null && dt.length > 0) {
        doc.writeln(dt);
      }

      if (mxClient.IS_VML) {
        doc.writeln('<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">');
      } else {
        if (document.compatMode === 'CSS1Compat') {
          doc.writeln('<!DOCTYPE html>');
        }

        doc.writeln('<html>');
      }

      doc.writeln('<head>');
      this.writeHead(doc, css);
      doc.writeln('</head>');
      doc.writeln('<body class="mxPage">');
    }

    // Computes the horizontal and vertical page count
    var bounds = this.graph.getGraphBounds().clone();
    var currentScale = this.graph.getView().getScale();
    var sc = currentScale / this.scale;
    var tr = this.graph.getView().getTranslate();

    // Uses the absolute origin with no offset for all printing
    if (!this.autoOrigin) {
      this.x0 -= tr.x * this.scale;
      this.y0 -= tr.y * this.scale;
      bounds.width += bounds.x;
      bounds.height += bounds.y;
      bounds.x = 0;
      bounds.y = 0;
      this.border = 0;
    }

    // Store the available page area
    var availableWidth = this.pageFormat.width - this.border * 2;
    var availableHeight = this.pageFormat.height - this.border * 2;

    // Adds margins to page format
    this.pageFormat.height += this.marginTop + this.marginBottom;

    // Compute the unscaled, untranslated bounds to find
    // the number of vertical and horizontal pages
    bounds.width /= sc;
    bounds.height /= sc;

    var hpages = Math.max(1, Math.ceil((bounds.width + this.x0) / availableWidth));
    var vpages = Math.max(1, Math.ceil((bounds.height + this.y0) / availableHeight));
    this.pageCount = hpages * vpages;

    var writePageSelector = mxUtils.bind(this, function () {
      if (this.pageSelector && (vpages > 1 || hpages > 1)) {
        var table = this.createPageSelector(vpages, hpages);
        doc.body.appendChild(table);

        // Implements position: fixed in IE quirks mode
        if (
          (mxClient.IS_IE && doc.documentMode == null) ||
          doc.documentMode == 5 ||
          doc.documentMode == 8 ||
          doc.documentMode == 7
        ) {
          table.style.position = 'absolute';

          var update = function () {
            table.style.top = (doc.body.scrollTop || doc.documentElement.scrollTop) + 10 + 'px';
          };

          mxEvent.addListener(this.wnd, 'scroll', function (evt) {
            update();
          });

          mxEvent.addListener(this.wnd, 'resize', function (evt) {
            update();
          });
        }
      }
    });

    var addPage = mxUtils.bind(this, function (div, addBreak) {
      // Border of the DIV (aka page) inside the document
      if (this.borderColor != null) {
        div.style.borderColor = this.borderColor;
        div.style.borderStyle = 'solid';
        div.style.borderWidth = '1px';
      }

      // Needs to be assigned directly because IE doesn't support
      // child selectors, eg. body > div { background: white; }
      div.style.background = this.backgroundColor;

      if (forcePageBreaks || addBreak) {
        div.style.pageBreakAfter = 'always';
      }

      // NOTE: We are dealing with cross-window DOM here, which
      // is a problem in IE, so we copy the HTML markup instead.
      // The underlying problem is that the graph display markup
      // creation (in mxShape, mxGraphView) is hardwired to using
      // document.createElement and hence we must use this document
      // to create the complete page and then copy it over to the
      // new window.document. This can be fixed later by using the
      // ownerDocument of the container in mxShape and mxGraphView.
      if (isNewWindow && (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE)) {
        // For some obscure reason, removing the DIV from the
        // parent before fetching its outerHTML has missing
        // fillcolor properties and fill children, so the div
        // must be removed afterwards to keep the fillcolors.
        doc.writeln(div.outerHTML);
        div.parentNode.removeChild(div);
      } else if (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE) {
        var clone = doc.createElement('div');
        clone.innerHTML = div.outerHTML;
        clone = clone.getElementsByTagName('div')[0];
        doc.body.appendChild(clone);
        div.parentNode.removeChild(div);
      } else {
        div.parentNode.removeChild(div);
        doc.body.appendChild(div);
      }

      if (forcePageBreaks || addBreak) {
        this.addPageBreak(doc);
      }
    });

    var cov = this.getCoverPages(this.pageFormat.width, this.pageFormat.height);

    if (cov != null) {
      for (var i = 0; i < cov.length; i++) {
        addPage(cov[i], true);
      }
    }

    var apx = this.getAppendices(this.pageFormat.width, this.pageFormat.height);

    // Appends each page to the page output for printing, making
    // sure there will be a page break after each page (ie. div)
    for (var i = 0; i < vpages; i++) {
      var dy =
        (i * availableHeight) / this.scale - this.y0 / this.scale + (bounds.y - tr.y * currentScale) / currentScale;

      for (var j = 0; j < hpages; j++) {
        if (this.wnd == null) {
          return null;
        }

        var dx =
          (j * availableWidth) / this.scale - this.x0 / this.scale + (bounds.x - tr.x * currentScale) / currentScale;
        var pageNum = i * hpages + j + 1;
        var clip = new mxRectangle(dx, dy, availableWidth, availableHeight);
        div = this.renderPage(
          this.pageFormat.width,
          this.pageFormat.height,
          0,
          0,
          mxUtils.bind(this, function (div) {
            this.addGraphFragment(-dx, -dy, this.scale, pageNum, div, clip);

            if (this.printBackgroundImage) {
              this.insertBackgroundImage(div, -dx, -dy);
            }
          }),
          pageNum
        );

        // Gives the page a unique ID for later accessing the page
        div.setAttribute('id', 'mxPage-' + pageNum);

        addPage(div, apx != null || i < vpages - 1 || j < hpages - 1);
      }
    }

    if (apx != null) {
      for (var i = 0; i < apx.length; i++) {
        addPage(apx[i], i < apx.length - 1);
      }
    }

    if (isNewWindow && !keepOpen) {
      this.closeDocument();
      writePageSelector();
    }

    this.wnd.focus();
  } catch (e) {
    // Removes the DIV from the document in case of an error
    if (div != null && div.parentNode != null) {
      div.parentNode.removeChild(div);
    }
  } finally {
    this.graph.cellRenderer.initializeOverlay = previousInitializeOverlay;
  }

  return this.wnd;
};

/**
 * Function: addPageBreak
 *
 * Adds a page break to the given document.
 */
mxPrintPreview.prototype.addPageBreak = function (doc) {
  var hr = doc.createElement('hr');
  hr.className = 'mxPageBreak';
  doc.body.appendChild(hr);
};

/**
 * Function: closeDocument
 *
 * Writes the closing tags for body and page after calling <writePostfix>.
 */
mxPrintPreview.prototype.closeDocument = function () {
  try {
    if (this.wnd != null && this.wnd.document != null) {
      var doc = this.wnd.document;

      this.writePostfix(doc);
      doc.writeln('</body>');
      doc.writeln('</html>');
      doc.close();

      // Removes all event handlers in the print output
      mxEvent.release(doc.body);
    }
  } catch (e) {
    // ignore any errors resulting from wnd no longer being available
  }
};

/**
 * Function: writeHead
 *
 * Writes the HEAD section into the given document, without the opening
 * and closing HEAD tags.
 */
mxPrintPreview.prototype.writeHead = function (doc, css) {
  if (this.title != null) {
    doc.writeln('<title>' + this.title + '</title>');
  }

  // Adds required namespaces
  if (mxClient.IS_VML) {
    doc.writeln('<style type="text/css">v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}</style>');
  }

  // Adds all required stylesheets
  // mxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc);

  // Removes horizontal rules and page selector from print output
  doc.writeln('<style type="text/css">');
  doc.writeln('@media print {');
  doc.writeln('  * { -webkit-print-color-adjust: exact; }');
  doc.writeln('  table.mxPageSelector { display: none; }');
  doc.writeln('  hr.mxPageBreak { display: none; }');
  doc.writeln('}');
  doc.writeln('@media screen {');

  // NOTE: position: fixed is not supported in IE, so the page selector
  // position (absolute) needs to be updated in IE (see below)
  doc.writeln(
    '  table.mxPageSelector { position: fixed; right: 10px; top: 10px;' +
      'font-family: Arial; font-size:10pt; border: solid 1px darkgray;' +
      'background: white; border-collapse:collapse; }'
  );
  doc.writeln('  table.mxPageSelector td { border: solid 1px gray; padding:4px; }');
  doc.writeln('  body.mxPage { background: gray; }');
  doc.writeln('}');

  if (css != null) {
    doc.writeln(css);
  }

  doc.writeln('</style>');
};

/**
 * Function: writePostfix
 *
 * Called before closing the body of the page. This implementation is empty.
 */
mxPrintPreview.prototype.writePostfix = function (doc) {
  // empty
};

/**
 * Function: createPageSelector
 *
 * Creates the page selector table.
 */
mxPrintPreview.prototype.createPageSelector = function (vpages, hpages) {
  var doc = this.wnd.document;
  var table = doc.createElement('table');
  table.className = 'mxPageSelector';
  table.setAttribute('border', '0');

  var tbody = doc.createElement('tbody');

  for (var i = 0; i < vpages; i++) {
    var row = doc.createElement('tr');

    for (var j = 0; j < hpages; j++) {
      var pageNum = i * hpages + j + 1;
      var cell = doc.createElement('td');
      var a = doc.createElement('a');
      a.setAttribute('href', '#mxPage-' + pageNum);

      // Workaround for FF where the anchor is appended to the URL of the original document
      if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC) {
        var js =
          "var page = document.getElementById('mxPage-" +
          pageNum +
          "');page.scrollIntoView(true);event.preventDefault();";
        a.setAttribute('onclick', js);
      }

      mxUtils.write(a, pageNum, doc);
      cell.appendChild(a);
      row.appendChild(cell);
    }

    tbody.appendChild(row);
  }

  table.appendChild(tbody);

  return table;
};

/**
 * Function: renderPage
 *
 * Creates a DIV that prints a single page of the given
 * graph using the given scale and returns the DIV that
 * represents the page.
 *
 * Parameters:
 *
 * w - Width of the page in pixels.
 * h - Height of the page in pixels.
 * dx - Optional horizontal page offset in pixels (used internally).
 * dy - Optional vertical page offset in pixels (used internally).
 * content - Callback that adds the HTML content to the inner div of a page.
 * Takes the inner div as the argument.
 * pageNumber - Integer representing the page number.
 */
mxPrintPreview.prototype.renderPage = function (w, h, dx, dy, content, pageNumber) {
  var doc = this.wnd.document;
  var div = document.createElement('div');
  var arg = null;

  try {
    // Workaround for ignored clipping in IE 9 standards
    // when printing with page breaks and HTML labels.
    if (dx != 0 || dy != 0) {
      div.style.position = 'relative';
      div.style.width = w + 'px';
      div.style.height = h + 'px';
      div.style.pageBreakInside = 'avoid';

      var innerDiv = document.createElement('div');
      innerDiv.style.position = 'relative';
      innerDiv.style.top = this.border + 'px';
      innerDiv.style.left = this.border + 'px';
      innerDiv.style.width = w - 2 * this.border + 'px';
      innerDiv.style.height = h - 2 * this.border + 'px';
      innerDiv.style.overflow = 'hidden';

      var viewport = document.createElement('div');
      viewport.style.position = 'relative';
      viewport.style.marginLeft = dx + 'px';
      viewport.style.marginTop = dy + 'px';

      // FIXME: IE8 standards output problems
      if (doc.documentMode == 8) {
        innerDiv.style.position = 'absolute';
        viewport.style.position = 'absolute';
      }

      if (doc.documentMode == 10) {
        viewport.style.width = '100%';
        viewport.style.height = '100%';
      }

      innerDiv.appendChild(viewport);
      div.appendChild(innerDiv);
      document.body.appendChild(div);
      arg = viewport;
    }
    // FIXME: IE10/11 too many pages
    else {
      div.style.width = w + 'px';
      div.style.height = h + 'px';
      div.style.overflow = 'hidden';
      div.style.pageBreakInside = 'avoid';

      // IE8 uses above branch currently
      if (doc.documentMode == 8) {
        div.style.position = 'relative';
      }

      var innerDiv = document.createElement('div');
      innerDiv.style.width = w - 2 * this.border + 'px';
      innerDiv.style.height = h - 2 * this.border + 'px';
      innerDiv.style.overflow = 'hidden';

      if (
        mxClient.IS_IE &&
        (doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7)
      ) {
        innerDiv.style.marginTop = this.border + 'px';
        innerDiv.style.marginLeft = this.border + 'px';
      } else {
        innerDiv.style.top = this.border + 'px';
        innerDiv.style.left = this.border + 'px';
      }

      if (this.graph.dialect == mxConstants.DIALECT_VML) {
        innerDiv.style.position = 'absolute';
      }

      div.appendChild(innerDiv);
      document.body.appendChild(div);
      arg = innerDiv;
    }
  } catch (e) {
    div.parentNode.removeChild(div);
    div = null;

    throw e;
  }

  content(arg);

  return div;
};

/**
 * Function: getRoot
 *
 * Returns the root cell for painting the graph.
 */
mxPrintPreview.prototype.getRoot = function () {
  var root = this.graph.view.currentRoot;

  if (root == null) {
    root = this.graph.getModel().getRoot();
  }

  return root;
};

/**
 * Function: useCssTransforms
 *
 * Returns true if CSS transforms should be used for scaling content.
 * This returns true if foreignObject is supported and we're not in Safari
 * as it has clipping bugs for transformed CSS content with foreignObjects.
 */
mxPrintPreview.prototype.useCssTransforms = function () {
  return !mxClient.NO_FO && !mxClient.IS_SF;
};

/**
 * Function: addGraphFragment
 *
 * Adds a graph fragment to the given div.
 *
 * Parameters:
 *
 * dx - Horizontal translation for the diagram.
 * dy - Vertical translation for the diagram.
 * scale - Scale for the diagram.
 * pageNumber - Number of the page to be rendered.
 * div - Div that contains the output.
 * clip - Contains the clipping rectangle as an <mxRectangle>.
 */
mxPrintPreview.prototype.addGraphFragment = function (dx, dy, scale, pageNumber, div, clip) {
  var view = this.graph.getView();
  var previousContainer = this.graph.container;
  this.graph.container = div;

  var canvas = view.getCanvas();
  var backgroundPane = view.getBackgroundPane();
  var drawPane = view.getDrawPane();
  var overlayPane = view.getOverlayPane();
  var realScale = scale;

  if (this.graph.dialect == mxConstants.DIALECT_SVG) {
    view.createSvg();

    // Uses CSS transform for scaling
    if (this.useCssTransforms()) {
      var g = view.getDrawPane().parentNode;
      var prev = g.getAttribute('transform');
      g.setAttribute('transformOrigin', '0 0');
      g.setAttribute('transform', 'scale(' + scale + ',' + scale + ')' + 'translate(' + dx + ',' + dy + ')');

      scale = 1;
      dx = 0;
      dy = 0;
    }
  } else if (this.graph.dialect == mxConstants.DIALECT_VML) {
    view.createVml();
  } else {
    view.createHtml();
  }

  // Disables events on the view
  var eventsEnabled = view.isEventsEnabled();
  view.setEventsEnabled(false);

  // Disables the graph to avoid cursors
  var graphEnabled = this.graph.isEnabled();
  this.graph.setEnabled(false);

  // Resets the translation
  var translate = view.getTranslate();
  view.translate = new mxPoint(dx, dy);

  // Redraws only states that intersect the clip
  var redraw = this.graph.cellRenderer.redraw;
  var states = view.states;
  var s = view.scale;

  // Gets the transformed clip for intersection check below
  if (this.clipping) {
    var tempClip = new mxRectangle(
      (clip.x + translate.x) * s,
      (clip.y + translate.y) * s,
      (clip.width * s) / realScale,
      (clip.height * s) / realScale
    );

    // Checks clipping rectangle for speedup
    // Must create terminal states for edge clipping even if terminal outside of clip
    this.graph.cellRenderer.redraw = function (state, force, rendering) {
      if (state != null) {
        // Gets original state from graph to find bounding box
        var orig = states.get(state.cell);

        if (orig != null) {
          var bbox = view.getBoundingBox(orig, false);

          // Stops rendering if outside clip for speedup but ignores
          // edge labels where width and height is set to 0
          if (bbox != null && bbox.width > 0 && bbox.height > 0 && !mxUtils.intersects(tempClip, bbox)) {
            return;
          }
        }
      }

      redraw.apply(this, arguments);
    };
  }

  var temp = null;

  try {
    // Creates the temporary cell states in the view and
    // draws them onto the temporary DOM nodes in the view
    var cells = [this.getRoot()];
    temp = new mxTemporaryCellStates(
      view,
      scale,
      cells,
      null,
      mxUtils.bind(this, function (state) {
        return this.getLinkForCellState(state);
      })
    );
  } finally {
    // Removes overlay pane with selection handles
    // controls and icons from the print output
    if (mxClient.IS_IE) {
      view.overlayPane.innerHTML = '';
      view.canvas.style.overflow = 'hidden';
      view.canvas.style.position = 'relative';
      view.canvas.style.top = this.marginTop + 'px';
      view.canvas.style.width = clip.width + 'px';
      view.canvas.style.height = clip.height + 'px';
    } else {
      // Removes everything but the SVG node
      var tmp = div.firstChild;

      while (tmp != null) {
        var next = tmp.nextSibling;
        var name = tmp.nodeName.toLowerCase();

        // Note: Width and height are required in FF 11
        if (name == 'svg') {
          tmp.style.overflow = 'hidden';
          tmp.style.position = 'relative';
          tmp.style.top = this.marginTop + 'px';
          tmp.setAttribute('width', clip.width);
          tmp.setAttribute('height', clip.height);
          tmp.style.width = '';
          tmp.style.height = '';
        }
        // Tries to fetch all text labels and only text labels
        else if (tmp.style.cursor != 'default' && name != 'div') {
          tmp.parentNode.removeChild(tmp);
        }

        tmp = next;
      }
    }

    // Puts background image behind SVG output
    if (this.printBackgroundImage) {
      var svgs = div.getElementsByTagName('svg');

      if (svgs.length > 0) {
        svgs[0].style.position = 'absolute';
      }
    }

    // Completely removes the overlay pane to remove more handles
    view.overlayPane.parentNode.removeChild(view.overlayPane);

    // Restores the state of the view
    this.graph.setEnabled(graphEnabled);
    this.graph.container = previousContainer;
    this.graph.cellRenderer.redraw = redraw;
    view.canvas = canvas;
    view.backgroundPane = backgroundPane;
    view.drawPane = drawPane;
    view.overlayPane = overlayPane;
    view.translate = translate;
    temp.destroy();
    view.setEventsEnabled(eventsEnabled);
  }
};

/**
 * Function: getLinkForCellState
 *
 * Returns the link for the given cell state. This returns null.
 */
mxPrintPreview.prototype.getLinkForCellState = function (state) {
  return this.graph.getLinkForCell(state.cell);
};

/**
 * Function: insertBackgroundImage
 *
 * Inserts the background image into the given div.
 */
mxPrintPreview.prototype.insertBackgroundImage = function (div, dx, dy) {
  var bg = this.graph.backgroundImage;

  if (bg != null) {
    var img = document.createElement('img');
    img.style.position = 'absolute';
    img.style.marginLeft = Math.round(dx * this.scale) + 'px';
    img.style.marginTop = Math.round(dy * this.scale) + 'px';
    img.setAttribute('width', Math.round(this.scale * bg.width));
    img.setAttribute('height', Math.round(this.scale * bg.height));
    img.src = bg.src;

    div.insertBefore(img, div.firstChild);
  }
};

/**
 * Function: getCoverPages
 *
 * Returns the pages to be added before the print output. This returns null.
 */
mxPrintPreview.prototype.getCoverPages = function () {
  return null;
};

/**
 * Function: getAppendices
 *
 * Returns the pages to be added after the print output. This returns null.
 */
mxPrintPreview.prototype.getAppendices = function () {
  return null;
};

/**
 * Function: print
 *
 * Opens the print preview and shows the print dialog.
 *
 * Parameters:
 *
 * css - Optional CSS string to be used in the head section.
 */
mxPrintPreview.prototype.print = function (css) {
  var wnd = this.open(css);

  if (wnd != null) {
    wnd.print();
  }
};

/**
 * Function: close
 *
 * Closes the print preview window.
 */
mxPrintPreview.prototype.close = function () {
  if (this.wnd != null) {
    this.wnd.close();
    this.wnd = null;
  }
};

/**
 * view/mxStylesheet.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxStylesheet
 *
 * Defines the appearance of the cells in a graph. See <putCellStyle> for an
 * example of creating a new cell style. It is recommended to use objects, not
 * arrays for holding cell styles. Existing styles can be cloned using
 * <mxUtils.clone> and turned into a string for debugging using
 * <mxUtils.toString>.
 *
 * Default Styles:
 *
 * The stylesheet contains two built-in styles, which are used if no style is
 * defined for a cell:
 *
 *   defaultVertex - Default style for vertices
 *   defaultEdge - Default style for edges
 *
 * Example:
 *
 * (code)
 * var vertexStyle = stylesheet.getDefaultVertexStyle();
 * vertexStyle[mxConstants.STYLE_ROUNDED] = true;
 * var edgeStyle = stylesheet.getDefaultEdgeStyle();
 * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;
 * (end)
 *
 * Modifies the built-in default styles.
 *
 * To avoid the default style for a cell, add a leading semicolon
 * to the style definition, eg.
 *
 * (code)
 * ;shadow=1
 * (end)
 *
 * Removing keys:
 *
 * For removing a key in a cell style of the form [stylename;|key=value;] the
 * special value none can be used, eg. highlight;fillColor=none
 *
 * See also the helper methods in mxUtils to modify strings of this format,
 * namely <mxUtils.setStyle>, <mxUtils.indexOfStylename>,
 * <mxUtils.addStylename>, <mxUtils.removeStylename>,
 * <mxUtils.removeAllStylenames> and <mxUtils.setStyleFlag>.
 *
 * Constructor: mxStylesheet
 *
 * Constructs a new stylesheet and assigns default styles.
 */
function mxStylesheet() {
  this.styles = new Object();

  this.putDefaultVertexStyle(this.createDefaultVertexStyle());
  this.putDefaultEdgeStyle(this.createDefaultEdgeStyle());
}

/**
 * Function: styles
 *
 * Maps from names to cell styles. Each cell style is a map of key,
 * value pairs.
 */
mxStylesheet.prototype.styles;

/**
 * Function: createDefaultVertexStyle
 *
 * Creates and returns the default vertex style.
 */
mxStylesheet.prototype.createDefaultVertexStyle = function () {
  var style = new Object();

  style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
  style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
  style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
  style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
  style[mxConstants.STYLE_FILLCOLOR] = 'rgb(255, 255, 255)';
  style[mxConstants.STYLE_STROKECOLOR] = 'rgb(0, 0, 0)';
  style[mxConstants.STYLE_FONTCOLOR] = '#774400';

  return style;
};

/**
 * Function: createDefaultEdgeStyle
 *
 * Creates and returns the default edge style.
 */
mxStylesheet.prototype.createDefaultEdgeStyle = function () {
  var style = new Object();

  style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR;
  style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
  style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
  style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
  style[mxConstants.STYLE_STROKECOLOR] = 'rgb(0, 0, 0)';
  style[mxConstants.STYLE_FONTCOLOR] = '#446299';

  return style;
};

/**
 * Function: putDefaultVertexStyle
 *
 * Sets the default style for vertices using defaultVertex as the
 * stylename.
 *
 * Parameters:
 * style - Key, value pairs that define the style.
 */
mxStylesheet.prototype.putDefaultVertexStyle = function (style) {
  this.putCellStyle('defaultVertex', style);
};

/**
 * Function: putDefaultEdgeStyle
 *
 * Sets the default style for edges using defaultEdge as the stylename.
 */
mxStylesheet.prototype.putDefaultEdgeStyle = function (style) {
  this.putCellStyle('defaultEdge', style);
};

/**
 * Function: getDefaultVertexStyle
 *
 * Returns the default style for vertices.
 */
mxStylesheet.prototype.getDefaultVertexStyle = function () {
  return this.styles['defaultVertex'];
};

/**
 * Function: getDefaultEdgeStyle
 *
 * Sets the default style for edges.
 */
mxStylesheet.prototype.getDefaultEdgeStyle = function () {
  return this.styles['defaultEdge'];
};

/**
 * Function: putCellStyle
 *
 * Stores the given map of key, value pairs under the given name in
 * <styles>.
 *
 * Example:
 *
 * The following example adds a new style called 'rounded' into an
 * existing stylesheet:
 *
 * (code)
 * var style = new Object();
 * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
 * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
 * style[mxConstants.STYLE_ROUNDED] = true;
 * graph.getStylesheet().putCellStyle('rounded', style);
 * (end)
 *
 * In the above example, the new style is an object. The possible keys of
 * the object are all the constants in <mxConstants> that start with STYLE
 * and the values are either JavaScript objects, such as
 * <mxPerimeter.RightAngleRectanglePerimeter> (which is in fact a function)
 * or expressions, such as true. Note that not all keys will be
 * interpreted by all shapes (eg. the line shape ignores the fill color).
 * The final call to this method associates the style with a name in the
 * stylesheet. The style is used in a cell with the following code:
 *
 * (code)
 * model.setStyle(cell, 'rounded');
 * (end)
 *
 * Parameters:
 *
 * name - Name for the style to be stored.
 * style - Key, value pairs that define the style.
 */
mxStylesheet.prototype.putCellStyle = function (name, style) {
  this.styles[name] = style;
};

/**
 * Function: getCellStyle
 *
 * Returns the cell style for the specified stylename or the given
 * defaultStyle if no style can be found for the given stylename.
 *
 * Parameters:
 *
 * name - String of the form [(stylename|key=value);] that represents the
 * style.
 * defaultStyle - Default style to be returned if no style can be found.
 */
mxStylesheet.prototype.getCellStyle = function (name, defaultStyle) {
  var style = defaultStyle;

  if (name != null && name.length > 0) {
    var pairs = name.split(';');

    if (style != null && name.charAt(0) != ';') {
      style = mxUtils.clone(style);
    } else {
      style = new Object();
    }

    // Parses each key, value pair into the existing style
    for (var i = 0; i < pairs.length; i++) {
      var tmp = pairs[i];
      var pos = tmp.indexOf('=');

      if (pos >= 0) {
        var key = tmp.substring(0, pos);
        var value = tmp.substring(pos + 1);

        if (value == mxConstants.NONE) {
          delete style[key];
        } else if (mxUtils.isNumeric(value)) {
          style[key] = parseFloat(value);
        } else {
          style[key] = value;
        }
      } else {
        // Merges the entries from a named style
        var tmpStyle = this.styles[tmp];

        if (tmpStyle != null) {
          for (var key in tmpStyle) {
            style[key] = tmpStyle[key];
          }
        }
      }
    }
  }

  return style;
};

/**
 * view/mxCellState.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCellState
 *
 * Represents the current state of a cell in a given <mxGraphView>.
 *
 * For edges, the edge label position is stored in <absoluteOffset>.
 *
 * The size for oversize labels can be retrieved using the boundingBox property
 * of the <text> field as shown below.
 *
 * (code)
 * var bbox = (state.text != null) ? state.text.boundingBox : null;
 * (end)
 *
 * Constructor: mxCellState
 *
 * Constructs a new object that represents the current state of the given
 * cell in the specified view.
 *
 * Parameters:
 *
 * view - <mxGraphView> that contains the state.
 * cell - <mxCell> that this state represents.
 * style - Array of key, value pairs that constitute the style.
 */
function mxCellState(view, cell, style) {
  this.view = view;
  this.cell = cell;
  this.style = style != null ? style : {};

  this.origin = new mxPoint();
  this.absoluteOffset = new mxPoint();
}

/**
 * Extends mxRectangle.
 */
mxCellState.prototype = new mxRectangle();
mxCellState.prototype.constructor = mxCellState;

/**
 * Variable: view
 *
 * Reference to the enclosing <mxGraphView>.
 */
mxCellState.prototype.view = null;

/**
 * Variable: cell
 *
 * Reference to the <mxCell> that is represented by this state.
 */
mxCellState.prototype.cell = null;

/**
 * Variable: style
 *
 * Contains an array of key, value pairs that represent the style of the
 * cell.
 */
mxCellState.prototype.style = null;

/**
 * Variable: invalidStyle
 *
 * Specifies if the style is invalid. Default is false.
 */
mxCellState.prototype.invalidStyle = false;

/**
 * Variable: invalid
 *
 * Specifies if the state is invalid. Default is true.
 */
mxCellState.prototype.invalid = true;

/**
 * Variable: origin
 *
 * <mxPoint> that holds the origin for all child cells. Default is a new
 * empty <mxPoint>.
 */
mxCellState.prototype.origin = null;

/**
 * Variable: absolutePoints
 *
 * Holds an array of <mxPoints> that represent the absolute points of an
 * edge.
 */
mxCellState.prototype.absolutePoints = null;

/**
 * Variable: absoluteOffset
 *
 * <mxPoint> that holds the absolute offset. For edges, this is the
 * absolute coordinates of the label position. For vertices, this is the
 * offset of the label relative to the top, left corner of the vertex.
 */
mxCellState.prototype.absoluteOffset = null;

/**
 * Variable: visibleSourceState
 *
 * Caches the visible source terminal state.
 */
mxCellState.prototype.visibleSourceState = null;

/**
 * Variable: visibleTargetState
 *
 * Caches the visible target terminal state.
 */
mxCellState.prototype.visibleTargetState = null;

/**
 * Variable: terminalDistance
 *
 * Caches the distance between the end points for an edge.
 */
mxCellState.prototype.terminalDistance = 0;

/**
 * Variable: length
 *
 * Caches the length of an edge.
 */
mxCellState.prototype.length = 0;

/**
 * Variable: segments
 *
 * Array of numbers that represent the cached length of each segment of the
 * edge.
 */
mxCellState.prototype.segments = null;

/**
 * Variable: shape
 *
 * Holds the <mxShape> that represents the cell graphically.
 */
mxCellState.prototype.shape = null;

/**
 * Variable: text
 *
 * Holds the <mxText> that represents the label of the cell. Thi smay be
 * null if the cell has no label.
 */
mxCellState.prototype.text = null;

/**
 * Variable: unscaledWidth
 *
 * Holds the unscaled width of the state.
 */
mxCellState.prototype.unscaledWidth = null;

/**
 * Variable: unscaledHeight
 *
 * Holds the unscaled height of the state.
 */
mxCellState.prototype.unscaledHeight = null;

/**
 * Function: getPerimeterBounds
 *
 * Returns the <mxRectangle> that should be used as the perimeter of the
 * cell.
 *
 * Parameters:
 *
 * border - Optional border to be added around the perimeter bounds.
 * bounds - Optional <mxRectangle> to be used as the initial bounds.
 */
mxCellState.prototype.getPerimeterBounds = function (border, bounds) {
  border = border || 0;
  bounds = bounds != null ? bounds : new mxRectangle(this.x, this.y, this.width, this.height);

  if (this.shape != null && this.shape.stencil != null && this.shape.stencil.aspect == 'fixed') {
    var aspect = this.shape.stencil.computeAspect(this.style, bounds.x, bounds.y, bounds.width, bounds.height);

    bounds.x = aspect.x;
    bounds.y = aspect.y;
    bounds.width = this.shape.stencil.w0 * aspect.width;
    bounds.height = this.shape.stencil.h0 * aspect.height;
  }

  if (border != 0) {
    bounds.grow(border);
  }

  return bounds;
};

/**
 * Function: setAbsoluteTerminalPoint
 *
 * Sets the first or last point in <absolutePoints> depending on isSource.
 *
 * Parameters:
 *
 * point - <mxPoint> that represents the terminal point.
 * isSource - Boolean that specifies if the first or last point should
 * be assigned.
 */
mxCellState.prototype.setAbsoluteTerminalPoint = function (point, isSource) {
  if (isSource) {
    if (this.absolutePoints == null) {
      this.absolutePoints = [];
    }

    if (this.absolutePoints.length == 0) {
      this.absolutePoints.push(point);
    } else {
      this.absolutePoints[0] = point;
    }
  } else {
    if (this.absolutePoints == null) {
      this.absolutePoints = [];
      this.absolutePoints.push(null);
      this.absolutePoints.push(point);
    } else if (this.absolutePoints.length == 1) {
      this.absolutePoints.push(point);
    } else {
      this.absolutePoints[this.absolutePoints.length - 1] = point;
    }
  }
};

/**
 * Function: setCursor
 *
 * Sets the given cursor on the shape and text shape.
 */
mxCellState.prototype.setCursor = function (cursor) {
  if (this.shape != null) {
    this.shape.setCursor(cursor);
  }

  if (this.text != null) {
    this.text.setCursor(cursor);
  }
};

/**
 * Function: getVisibleTerminal
 *
 * Returns the visible source or target terminal cell.
 *
 * Parameters:
 *
 * source - Boolean that specifies if the source or target cell should be
 * returned.
 */
mxCellState.prototype.getVisibleTerminal = function (source) {
  var tmp = this.getVisibleTerminalState(source);

  return tmp != null ? tmp.cell : null;
};

/**
 * Function: getVisibleTerminalState
 *
 * Returns the visible source or target terminal state.
 *
 * Parameters:
 *
 * source - Boolean that specifies if the source or target state should be
 * returned.
 */
mxCellState.prototype.getVisibleTerminalState = function (source) {
  return source ? this.visibleSourceState : this.visibleTargetState;
};

/**
 * Function: setVisibleTerminalState
 *
 * Sets the visible source or target terminal state.
 *
 * Parameters:
 *
 * terminalState - <mxCellState> that represents the terminal.
 * source - Boolean that specifies if the source or target state should be set.
 */
mxCellState.prototype.setVisibleTerminalState = function (terminalState, source) {
  if (source) {
    this.visibleSourceState = terminalState;
  } else {
    this.visibleTargetState = terminalState;
  }
};

/**
 * Function: getCellBounds
 *
 * Returns the unscaled, untranslated bounds.
 */
mxCellState.prototype.getCellBounds = function () {
  return this.cellBounds;
};

/**
 * Function: getPaintBounds
 *
 * Returns the unscaled, untranslated paint bounds. This is the same as
 * <getCellBounds> but with a 90 degree rotation if the shape's
 * isPaintBoundsInverted returns true.
 */
mxCellState.prototype.getPaintBounds = function () {
  return this.paintBounds;
};

/**
 * Function: updateCachedBounds
 *
 * Updates the cellBounds and paintBounds.
 */
mxCellState.prototype.updateCachedBounds = function () {
  var tr = this.view.translate;
  var s = this.view.scale;
  this.cellBounds = new mxRectangle(this.x / s - tr.x, this.y / s - tr.y, this.width / s, this.height / s);
  this.paintBounds = mxRectangle.fromRectangle(this.cellBounds);

  if (this.shape != null && this.shape.isPaintBoundsInverted()) {
    this.paintBounds.rotate90();
  }
};

/**
 * Destructor: setState
 *
 * Copies all fields from the given state to this state.
 */
mxCellState.prototype.setState = function (state) {
  this.view = state.view;
  this.cell = state.cell;
  this.style = state.style;
  this.absolutePoints = state.absolutePoints;
  this.origin = state.origin;
  this.absoluteOffset = state.absoluteOffset;
  this.boundingBox = state.boundingBox;
  this.terminalDistance = state.terminalDistance;
  this.segments = state.segments;
  this.length = state.length;
  this.x = state.x;
  this.y = state.y;
  this.width = state.width;
  this.height = state.height;
  this.unscaledWidth = state.unscaledWidth;
  this.unscaledHeight = state.unscaledHeight;
};

/**
 * Function: clone
 *
 * Returns a clone of this <mxPoint>.
 */
mxCellState.prototype.clone = function () {
  var clone = new mxCellState(this.view, this.cell, this.style);

  // Clones the absolute points
  if (this.absolutePoints != null) {
    clone.absolutePoints = [];

    for (var i = 0; i < this.absolutePoints.length; i++) {
      clone.absolutePoints[i] = this.absolutePoints[i].clone();
    }
  }

  if (this.origin != null) {
    clone.origin = this.origin.clone();
  }

  if (this.absoluteOffset != null) {
    clone.absoluteOffset = this.absoluteOffset.clone();
  }

  if (this.boundingBox != null) {
    clone.boundingBox = this.boundingBox.clone();
  }

  clone.terminalDistance = this.terminalDistance;
  clone.segments = this.segments;
  clone.length = this.length;
  clone.x = this.x;
  clone.y = this.y;
  clone.width = this.width;
  clone.height = this.height;
  clone.unscaledWidth = this.unscaledWidth;
  clone.unscaledHeight = this.unscaledHeight;

  return clone;
};

/**
 * Destructor: destroy
 *
 * Destroys the state and all associated resources.
 */
mxCellState.prototype.destroy = function () {
  this.view.graph.cellRenderer.destroy(this);
};

/**
 * view/mxGraphSelectionModel.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGraphSelectionModel
 *
 * Implements the selection model for a graph. Here is a listener that handles
 * all removed selection cells.
 *
 * (code)
 * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt)
 * {
 *   var cells = evt.getProperty('added');
 *
 *   for (var i = 0; i < cells.length; i++)
 *   {
 *     // Handle cells[i]...
 *   }
 * });
 * (end)
 *
 * Event: mxEvent.UNDO
 *
 * Fires after the selection was changed in <changeSelection>. The
 * <code>edit</code> property contains the <mxUndoableEdit> which contains the
 * <mxSelectionChange>.
 *
 * Event: mxEvent.CHANGE
 *
 * Fires after the selection changes by executing an <mxSelectionChange>. The
 * <code>added</code> and <code>removed</code> properties contain arrays of
 * cells that have been added to or removed from the selection, respectively.
 * The names are inverted due to historic reasons. This cannot be changed.
 *
 * Constructor: mxGraphSelectionModel
 *
 * Constructs a new graph selection model for the given <mxGraph>.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 */
function mxGraphSelectionModel(graph) {
  this.graph = graph;
  this.cells = [];
}

/**
 * Extends mxEventSource.
 */
mxGraphSelectionModel.prototype = new mxEventSource();
mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel;

/**
 * Variable: doneResource
 *
 * Specifies the resource key for the status message after a long operation.
 * If the resource for this key does not exist then the value is used as
 * the status message. Default is 'done'.
 */
mxGraphSelectionModel.prototype.doneResource = mxClient.language != 'none' ? 'done' : '';

/**
 * Variable: updatingSelectionResource
 *
 * Specifies the resource key for the status message while the selection is
 * being updated. If the resource for this key does not exist then the
 * value is used as the status message. Default is 'updatingSelection'.
 */
mxGraphSelectionModel.prototype.updatingSelectionResource = mxClient.language != 'none' ? 'updatingSelection' : '';

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxGraphSelectionModel.prototype.graph = null;

/**
 * Variable: singleSelection
 *
 * Specifies if only one selected item at a time is allowed.
 * Default is false.
 */
mxGraphSelectionModel.prototype.singleSelection = false;

/**
 * Function: isSingleSelection
 *
 * Returns <singleSelection> as a boolean.
 */
mxGraphSelectionModel.prototype.isSingleSelection = function () {
  return this.singleSelection;
};

/**
 * Function: setSingleSelection
 *
 * Sets the <singleSelection> flag.
 *
 * Parameters:
 *
 * singleSelection - Boolean that specifies the new value for
 * <singleSelection>.
 */
mxGraphSelectionModel.prototype.setSingleSelection = function (singleSelection) {
  this.singleSelection = singleSelection;
};

/**
 * Function: isSelected
 *
 * Returns true if the given <mxCell> is selected.
 */
mxGraphSelectionModel.prototype.isSelected = function (cell) {
  if (cell != null) {
    return mxUtils.indexOf(this.cells, cell) >= 0;
  }

  return false;
};

/**
 * Function: isEmpty
 *
 * Returns true if no cells are currently selected.
 */
mxGraphSelectionModel.prototype.isEmpty = function () {
  return this.cells.length == 0;
};

/**
 * Function: clear
 *
 * Clears the selection and fires a <change> event if the selection was not
 * empty.
 */
mxGraphSelectionModel.prototype.clear = function () {
  this.changeSelection(null, this.cells);
};

/**
 * Function: setCell
 *
 * Selects the specified <mxCell> using <setCells>.
 *
 * Parameters:
 *
 * cell - <mxCell> to be selected.
 */
mxGraphSelectionModel.prototype.setCell = function (cell) {
  if (cell != null) {
    this.setCells([cell]);
  }
};

/**
 * Function: setCells
 *
 * Selects the given array of <mxCells> and fires a <change> event.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be selected.
 */
mxGraphSelectionModel.prototype.setCells = function (cells) {
  if (cells != null) {
    if (this.singleSelection) {
      cells = [this.getFirstSelectableCell(cells)];
    }

    var tmp = [];

    for (var i = 0; i < cells.length; i++) {
      if (this.graph.isCellSelectable(cells[i])) {
        tmp.push(cells[i]);
      }
    }

    this.changeSelection(tmp, this.cells);
  }
};

/**
 * Function: getFirstSelectableCell
 *
 * Returns the first selectable cell in the given array of cells.
 */
mxGraphSelectionModel.prototype.getFirstSelectableCell = function (cells) {
  if (cells != null) {
    for (var i = 0; i < cells.length; i++) {
      if (this.graph.isCellSelectable(cells[i])) {
        return cells[i];
      }
    }
  }

  return null;
};

/**
 * Function: addCell
 *
 * Adds the given <mxCell> to the selection and fires a <select> event.
 *
 * Parameters:
 *
 * cell - <mxCell> to add to the selection.
 */
mxGraphSelectionModel.prototype.addCell = function (cell) {
  if (cell != null) {
    this.addCells([cell]);
  }
};

/**
 * Function: addCells
 *
 * Adds the given array of <mxCells> to the selection and fires a <select>
 * event.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to add to the selection.
 */
mxGraphSelectionModel.prototype.addCells = function (cells) {
  if (cells != null) {
    var remove = null;

    if (this.singleSelection) {
      remove = this.cells;
      cells = [this.getFirstSelectableCell(cells)];
    }

    var tmp = [];

    for (var i = 0; i < cells.length; i++) {
      if (!this.isSelected(cells[i]) && this.graph.isCellSelectable(cells[i])) {
        tmp.push(cells[i]);
      }
    }

    this.changeSelection(tmp, remove);
  }
};

/**
 * Function: removeCell
 *
 * Removes the specified <mxCell> from the selection and fires a <select>
 * event for the remaining cells.
 *
 * Parameters:
 *
 * cell - <mxCell> to remove from the selection.
 */
mxGraphSelectionModel.prototype.removeCell = function (cell) {
  if (cell != null) {
    this.removeCells([cell]);
  }
};

/**
 * Function: removeCells
 */
mxGraphSelectionModel.prototype.removeCells = function (cells) {
  if (cells != null) {
    var tmp = [];

    for (var i = 0; i < cells.length; i++) {
      if (this.isSelected(cells[i])) {
        tmp.push(cells[i]);
      }
    }

    this.changeSelection(null, tmp);
  }
};

/**
 * Function: changeSelection
 *
 * Adds/removes the specified arrays of <mxCell> to/from the selection.
 *
 * Parameters:
 *
 * added - Array of <mxCell> to add to the selection.
 * remove - Array of <mxCell> to remove from the selection.
 */
mxGraphSelectionModel.prototype.changeSelection = function (added, removed) {
  if (
    (added != null && added.length > 0 && added[0] != null) ||
    (removed != null && removed.length > 0 && removed[0] != null)
  ) {
    var change = new mxSelectionChange(this, added, removed);
    change.execute();
    var edit = new mxUndoableEdit(this, false);
    edit.add(change);
    this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
  }
};

/**
 * Function: cellAdded
 *
 * Inner callback to add the specified <mxCell> to the selection. No event
 * is fired in this implementation.
 *
 * Paramters:
 *
 * cell - <mxCell> to add to the selection.
 */
mxGraphSelectionModel.prototype.cellAdded = function (cell) {
  if (cell != null && !this.isSelected(cell)) {
    this.cells.push(cell);
  }
};

/**
 * Function: cellRemoved
 *
 * Inner callback to remove the specified <mxCell> from the selection. No
 * event is fired in this implementation.
 *
 * Parameters:
 *
 * cell - <mxCell> to remove from the selection.
 */
mxGraphSelectionModel.prototype.cellRemoved = function (cell) {
  if (cell != null) {
    var index = mxUtils.indexOf(this.cells, cell);

    if (index >= 0) {
      this.cells.splice(index, 1);
    }
  }
};

/**
 * Class: mxSelectionChange
 *
 * Action to change the current root in a view.
 *
 * Constructor: mxCurrentRootChange
 *
 * Constructs a change of the current root in the given view.
 */
function mxSelectionChange(selectionModel, added, removed) {
  this.selectionModel = selectionModel;
  this.added = added != null ? added.slice() : null;
  this.removed = removed != null ? removed.slice() : null;
}

/**
 * Function: execute
 *
 * Changes the current root of the view.
 */
mxSelectionChange.prototype.execute = function () {
  var t0 = mxLog.enter('mxSelectionChange.execute');
  window.status =
    mxResources.get(this.selectionModel.updatingSelectionResource) || this.selectionModel.updatingSelectionResource;

  if (this.removed != null) {
    for (var i = 0; i < this.removed.length; i++) {
      this.selectionModel.cellRemoved(this.removed[i]);
    }
  }

  if (this.added != null) {
    for (var i = 0; i < this.added.length; i++) {
      this.selectionModel.cellAdded(this.added[i]);
    }
  }

  var tmp = this.added;
  this.added = this.removed;
  this.removed = tmp;

  window.status = mxResources.get(this.selectionModel.doneResource) || this.selectionModel.doneResource;
  mxLog.leave('mxSelectionChange.execute', t0);

  this.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE, 'added', this.added, 'removed', this.removed));
};

/**
 * view/mxCellEditor.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCellEditor
 *
 * In-place editor for the graph. To control this editor, use
 * <mxGraph.invokesStopCellEditing>, <mxGraph.enterStopsCellEditing> and
 * <mxGraph.escapeEnabled>. If <mxGraph.enterStopsCellEditing> is true then
 * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and
 * escape keys can always be used to stop editing.
 *
 * To customize the location of the textbox in the graph, override
 * <getEditorBounds> as follows:
 *
 * (code)
 * graph.cellEditor.getEditorBounds = function(state)
 * {
 *   var result = mxCellEditor.prototype.getEditorBounds.apply(this, arguments);
 *
 *   if (this.graph.getModel().isEdge(state.cell))
 *   {
 *     result.x = state.getCenterX() - result.width / 2;
 *     result.y = state.getCenterY() - result.height / 2;
 *   }
 *
 *   return result;
 * };
 * (end)
 *
 * Note that this hook is only called if <autoSize> is false. If <autoSize> is true,
 * then <mxShape.getLabelBounds> is used to compute the current bounds of the textbox.
 *
 * The textarea uses the mxCellEditor CSS class. You can modify this class in
 * your custom CSS. Note: You should modify the CSS after loading the client
 * in the page.
 *
 * Example:
 *
 * To only allow numeric input in the in-place editor, use the following code.
 *
 * (code)
 * var text = graph.cellEditor.textarea;
 *
 * mxEvent.addListener(text, 'keydown', function (evt)
 * {
 *   if (!(evt.keyCode >= 48 && evt.keyCode <= 57) &&
 *       !(evt.keyCode >= 96 && evt.keyCode <= 105))
 *   {
 *     mxEvent.consume(evt);
 *   }
 * });
 * (end)
 *
 * Placeholder:
 *
 * To implement a placeholder for cells without a label, use the
 * <emptyLabelText> variable.
 *
 * Resize in Chrome:
 *
 * Resize of the textarea is disabled by default. If you want to enable
 * this feature extend <init> and set this.textarea.style.resize = ''.
 *
 * To start editing on a key press event, the container of the graph
 * should have focus or a focusable parent should be used to add the
 * key press handler as follows.
 *
 * (code)
 * mxEvent.addListener(graph.container, 'keypress', mxUtils.bind(this, function(evt)
 * {
 *   if (!graph.isEditing() && !graph.isSelectionEmpty() && evt.which !== 0 &&
 *       !mxEvent.isAltDown(evt) && !mxEvent.isControlDown(evt) && !mxEvent.isMetaDown(evt))
 *   {
 *     graph.startEditing();
 *
 *     if (mxClient.IS_FF)
 *     {
 *       graph.cellEditor.textarea.value = String.fromCharCode(evt.which);
 *     }
 *   }
 * }));
 * (end)
 *
 * To allow focus for a DIV, and hence to receive key press events, some browsers
 * require it to have a valid tabindex attribute. In this case the following
 * code may be used to keep the container focused.
 *
 * (code)
 * var graphFireMouseEvent = graph.fireMouseEvent;
 * graph.fireMouseEvent = function(evtName, me, sender)
 * {
 *   if (evtName == mxEvent.MOUSE_DOWN)
 *   {
 *     this.container.focus();
 *   }
 *
 *   graphFireMouseEvent.apply(this, arguments);
 * };
 * (end)
 *
 * Constructor: mxCellEditor
 *
 * Constructs a new in-place editor for the specified graph.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 */
function mxCellEditor(graph) {
  this.graph = graph;

  // Stops editing after zoom changes
  this.zoomHandler = mxUtils.bind(this, function () {
    if (this.graph.isEditing()) {
      this.resize();
    }
  });

  this.graph.view.addListener(mxEvent.SCALE, this.zoomHandler);
  this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.zoomHandler);

  // Adds handling of deleted cells while editing
  this.changeHandler = mxUtils.bind(this, function (sender) {
    if (this.editingCell != null && this.graph.getView().getState(this.editingCell) == null) {
      this.stopEditing(true);
    }
  });

  this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
}

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxCellEditor.prototype.graph = null;

/**
 * Variable: textarea
 *
 * Holds the DIV that is used for text editing. Note that this may be null before the first
 * edit. Instantiated in <init>.
 */
mxCellEditor.prototype.textarea = null;

/**
 * Variable: editingCell
 *
 * Reference to the <mxCell> that is currently being edited.
 */
mxCellEditor.prototype.editingCell = null;

/**
 * Variable: trigger
 *
 * Reference to the event that was used to start editing.
 */
mxCellEditor.prototype.trigger = null;

/**
 * Variable: modified
 *
 * Specifies if the label has been modified.
 */
mxCellEditor.prototype.modified = false;

/**
 * Variable: autoSize
 *
 * Specifies if the textarea should be resized while the text is being edited.
 * Default is true.
 */
mxCellEditor.prototype.autoSize = true;

/**
 * Variable: selectText
 *
 * Specifies if the text should be selected when editing starts. Default is
 * true.
 */
mxCellEditor.prototype.selectText = true;

/**
 * Variable: emptyLabelText
 *
 * Text to be displayed for empty labels. Default is '' or '<br>' in Firefox as
 * a workaround for the missing cursor bug for empty content editable. This can
 * be set to eg. "[Type Here]" to easier visualize editing of empty labels. The
 * value is only displayed before the first keystroke and is never used as the
 * actual editing value.
 */
mxCellEditor.prototype.emptyLabelText = mxClient.IS_FF ? '<br>' : '';

/**
 * Variable: escapeCancelsEditing
 *
 * If true, pressing the escape key will stop editing and not accept the new
 * value. Change this to false to accept the new value on escape, and cancel
 * editing on Shift+Escape instead. Default is true.
 */
mxCellEditor.prototype.escapeCancelsEditing = true;

/**
 * Variable: textNode
 *
 * Reference to the label DOM node that has been hidden.
 */
mxCellEditor.prototype.textNode = '';

/**
 * Variable: zIndex
 *
 * Specifies the zIndex for the textarea. Default is 5.
 */
mxCellEditor.prototype.zIndex = 5;

/**
 * Variable: minResize
 *
 * Defines the minimum width and height to be used in <resize>. Default is 0x20px.
 */
mxCellEditor.prototype.minResize = new mxRectangle(0, 20);

/**
 * Variable: wordWrapPadding
 *
 * Correction factor for word wrapping width. Default is 2 in quirks, 0 in IE
 * 11 and 1 in all other browsers and modes.
 */
mxCellEditor.prototype.wordWrapPadding = mxClient.IS_QUIRKS ? 2 : !mxClient.IS_IE11 ? 1 : 0;

/**
 * Variable: blurEnabled
 *
 * If <focusLost> should be called if <textarea> loses the focus. Default is false.
 */
mxCellEditor.prototype.blurEnabled = false;

/**
 * Variable: initialValue
 *
 * Holds the initial editing value to check if the current value was modified.
 */
mxCellEditor.prototype.initialValue = null;

/**
 * Variable: align
 *
 * Holds the current temporary horizontal alignment for the cell style. If this
 * is modified then the current text alignment is changed and the cell style is
 * updated when the value is applied.
 */
mxCellEditor.prototype.align = null;

/**
 * Function: init
 *
 * Creates the <textarea> and installs the event listeners. The key handler
 * updates the <modified> state.
 */
mxCellEditor.prototype.init = function () {
  this.textarea = document.createElement('div');
  this.textarea.className = 'mxCellEditor mxPlainTextEditor';
  this.textarea.contentEditable = true;

  // Workaround for selection outside of DIV if height is 0
  if (mxClient.IS_GC) {
    this.textarea.style.minHeight = '1em';
  }

  this.textarea.style.position = this.isLegacyEditor() ? 'absolute' : 'relative';
  this.installListeners(this.textarea);
};

/**
 * Function: applyValue
 *
 * Called in <stopEditing> if cancel is false to invoke <mxGraph.labelChanged>.
 */
mxCellEditor.prototype.applyValue = function (state, value) {
  this.graph.labelChanged(state.cell, value, this.trigger);
};

/**
 * Function: setAlign
 *
 * Sets the temporary horizontal alignment for the current editing session.
 */
mxCellEditor.prototype.setAlign = function (align) {
  if (this.textarea != null) {
    this.textarea.style.textAlign = align;
  }

  this.align = align;
  this.resize();
};

/**
 * Function: getInitialValue
 *
 * Gets the initial editing value for the given cell.
 */
mxCellEditor.prototype.getInitialValue = function (state, trigger) {
  var result = mxUtils.htmlEntities(this.graph.getEditingValue(state.cell, trigger), false);

  // Workaround for trailing line breaks being ignored in the editor
  if (!mxClient.IS_QUIRKS && document.documentMode != 8 && document.documentMode != 9 && document.documentMode != 10) {
    result = mxUtils.replaceTrailingNewlines(result, '<div><br></div>');
  }

  return result.replace(/\n/g, '<br>');
};

/**
 * Function: getCurrentValue
 *
 * Returns the current editing value.
 */
mxCellEditor.prototype.getCurrentValue = function (state) {
  return mxUtils.extractTextWithWhitespace(this.textarea.childNodes);
};

/**
 * Function: isCancelEditingKeyEvent
 *
 * Returns true if <escapeCancelsEditing> is true and shift, control and meta
 * are not pressed.
 */
mxCellEditor.prototype.isCancelEditingKeyEvent = function (evt) {
  return this.escapeCancelsEditing || mxEvent.isShiftDown(evt) || mxEvent.isControlDown(evt) || mxEvent.isMetaDown(evt);
};

/**
 * Function: installListeners
 *
 * Installs listeners for focus, change and standard key event handling.
 */
mxCellEditor.prototype.installListeners = function (elt) {
  // Applies value if text is dragged
  // LATER: Gesture mouse events ignored for starting move
  mxEvent.addListener(
    elt,
    'dragstart',
    mxUtils.bind(this, function (evt) {
      this.graph.stopEditing(false);
      mxEvent.consume(evt);
    })
  );

  // Applies value if focus is lost
  mxEvent.addListener(
    elt,
    'blur',
    mxUtils.bind(this, function (evt) {
      if (this.blurEnabled) {
        this.focusLost(evt);
      }
    })
  );

  // Updates modified state and handles placeholder text
  mxEvent.addListener(
    elt,
    'keydown',
    mxUtils.bind(this, function (evt) {
      if (!mxEvent.isConsumed(evt)) {
        if (this.isStopEditingEvent(evt)) {
          this.graph.stopEditing(false);
          mxEvent.consume(evt);
        } else if (evt.keyCode == 27 /* Escape */) {
          this.graph.stopEditing(this.isCancelEditingKeyEvent(evt));
          mxEvent.consume(evt);
        }
      }
    })
  );

  // Keypress only fires if printable key was pressed and handles removing the empty placeholder
  var keypressHandler = mxUtils.bind(this, function (evt) {
    if (this.editingCell != null) {
      // Clears the initial empty label on the first keystroke
      // and workaround for FF which fires keypress for delete and backspace
      if (
        this.clearOnChange &&
        elt.innerHTML == this.getEmptyLabelText() &&
        (!mxClient.IS_FF || (evt.keyCode != 8 /* Backspace */ && evt.keyCode != 46)) /* Delete */
      ) {
        this.clearOnChange = false;
        elt.innerHTML = '';
      }
    }
  });

  mxEvent.addListener(elt, 'keypress', keypressHandler);
  mxEvent.addListener(elt, 'paste', keypressHandler);

  // Handler for updating the empty label text value after a change
  var keyupHandler = mxUtils.bind(this, function (evt) {
    if (this.editingCell != null) {
      // Uses an optional text value for sempty labels which is cleared
      // when the first keystroke appears. This makes it easier to see
      // that a label is being edited even if the label is empty.
      // In Safari and FF, an empty text is represented by <BR> which isn't enough to force a valid size
      if (this.textarea.innerHTML.length == 0 || this.textarea.innerHTML == '<br>') {
        this.textarea.innerHTML = this.getEmptyLabelText();
        this.clearOnChange = this.textarea.innerHTML.length > 0;
      } else {
        this.clearOnChange = false;
      }
    }
  });

  mxEvent.addListener(elt, !mxClient.IS_IE11 && !mxClient.IS_IE ? 'input' : 'keyup', keyupHandler);
  mxEvent.addListener(elt, 'cut', keyupHandler);
  mxEvent.addListener(elt, 'paste', keyupHandler);

  // Adds automatic resizing of the textbox while typing using input, keyup and/or DOM change events
  var evtName = !mxClient.IS_IE11 && !mxClient.IS_IE ? 'input' : 'keydown';

  var resizeHandler = mxUtils.bind(this, function (evt) {
    if (this.editingCell != null && this.autoSize && !mxEvent.isConsumed(evt)) {
      // Asynchronous is needed for keydown and shows better results for input events overall
      // (ie non-blocking and cases where the offsetWidth/-Height was wrong at this time)
      if (this.resizeThread != null) {
        window.clearTimeout(this.resizeThread);
      }

      this.resizeThread = window.setTimeout(
        mxUtils.bind(this, function () {
          this.resizeThread = null;
          this.resize();
        }),
        0
      );
    }
  });

  mxEvent.addListener(elt, evtName, resizeHandler);
  mxEvent.addListener(window, 'resize', resizeHandler);

  if (document.documentMode >= 9) {
    mxEvent.addListener(elt, 'DOMNodeRemoved', resizeHandler);
    mxEvent.addListener(elt, 'DOMNodeInserted', resizeHandler);
  } else {
    mxEvent.addListener(elt, 'cut', resizeHandler);
    mxEvent.addListener(elt, 'paste', resizeHandler);
  }
};

/**
 * Function: isStopEditingEvent
 *
 * Returns true if the given keydown event should stop cell editing. This
 * returns true if F2 is pressed of if <mxGraph.enterStopsCellEditing> is true
 * and enter is pressed without control or shift.
 */
mxCellEditor.prototype.isStopEditingEvent = function (evt) {
  return (
    evt.keyCode == 113 /* F2 */ ||
    (this.graph.isEnterStopsCellEditing() &&
      evt.keyCode == 13 /* Enter */ &&
      !mxEvent.isControlDown(evt) &&
      !mxEvent.isShiftDown(evt))
  );
};

/**
 * Function: isEventSource
 *
 * Returns true if this editor is the source for the given native event.
 */
mxCellEditor.prototype.isEventSource = function (evt) {
  return mxEvent.getSource(evt) == this.textarea;
};

/**
 * Function: resize
 *
 * Returns <modified>.
 */
mxCellEditor.prototype.resize = function () {
  var state = this.graph.getView().getState(this.editingCell);

  if (state == null) {
    this.stopEditing(true);
  } else if (this.textarea != null) {
    var isEdge = this.graph.getModel().isEdge(state.cell);
    var scale = this.graph.getView().scale;
    var m = null;

    if (!this.autoSize || state.style[mxConstants.STYLE_OVERFLOW] == 'fill') {
      // Specifies the bounds of the editor box
      this.bounds = this.getEditorBounds(state);
      this.textarea.style.width = Math.round(this.bounds.width / scale) + 'px';
      this.textarea.style.height = Math.round(this.bounds.height / scale) + 'px';

      // FIXME: Offset when scaled
      if (document.documentMode == 8 || mxClient.IS_QUIRKS) {
        this.textarea.style.left = Math.round(this.bounds.x) + 'px';
        this.textarea.style.top = Math.round(this.bounds.y) + 'px';
      } else {
        this.textarea.style.left = Math.max(0, Math.round(this.bounds.x + 1)) + 'px';
        this.textarea.style.top = Math.max(0, Math.round(this.bounds.y + 1)) + 'px';
      }

      // Installs native word wrapping and avoids word wrap for empty label placeholder
      if (
        this.graph.isWrapping(state.cell) &&
        (this.bounds.width >= 2 || this.bounds.height >= 2) &&
        this.textarea.innerHTML != this.getEmptyLabelText()
      ) {
        this.textarea.style.wordWrap = mxConstants.WORD_WRAP;
        this.textarea.style.whiteSpace = 'normal';

        if (state.style[mxConstants.STYLE_OVERFLOW] != 'fill') {
          this.textarea.style.width = Math.round(this.bounds.width / scale) + this.wordWrapPadding + 'px';
        }
      } else {
        this.textarea.style.whiteSpace = 'nowrap';

        if (state.style[mxConstants.STYLE_OVERFLOW] != 'fill') {
          this.textarea.style.width = '';
        }
      }
    } else {
      var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);
      m = state.text != null && this.align == null ? state.text.margin : null;

      if (m == null) {
        m = mxUtils.getAlignmentAsPoint(
          this.align || mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER),
          mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE)
        );
      }

      if (isEdge) {
        this.bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y, 0, 0);

        if (lw != null) {
          var tmp = (parseFloat(lw) + 2) * scale;
          this.bounds.width = tmp;
          this.bounds.x += m.x * tmp;
        }
      } else {
        var bds = mxRectangle.fromRectangle(state);
        var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
        var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);

        bds =
          state.shape != null && hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE
            ? state.shape.getLabelBounds(bds)
            : bds;

        if (lw != null) {
          bds.width = parseFloat(lw) * scale;
        }

        if (!state.view.graph.cellRenderer.legacySpacing || state.style[mxConstants.STYLE_OVERFLOW] != 'width') {
          var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 2) * scale;
          var spacingTop =
            (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0) + mxText.prototype.baseSpacingTop) * scale +
            spacing;
          var spacingRight =
            (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0) + mxText.prototype.baseSpacingRight) * scale +
            spacing;
          var spacingBottom =
            (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) + mxText.prototype.baseSpacingBottom) *
              scale +
            spacing;
          var spacingLeft =
            (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0) + mxText.prototype.baseSpacingLeft) * scale +
            spacing;

          var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
          var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);

          bds = new mxRectangle(
            bds.x + spacingLeft,
            bds.y + spacingTop,
            bds.width - (hpos == mxConstants.ALIGN_CENTER && lw == null ? spacingLeft + spacingRight : 0),
            bds.height - (vpos == mxConstants.ALIGN_MIDDLE ? spacingTop + spacingBottom : 0)
          );
        }

        this.bounds = new mxRectangle(
          bds.x + state.absoluteOffset.x,
          bds.y + state.absoluteOffset.y,
          bds.width,
          bds.height
        );
      }

      // Needed for word wrap inside text blocks with oversize lines to match the final result where
      // the width of the longest line is used as the reference for text alignment in the cell
      // TODO: Fix word wrapping preview for edge labels in helloworld.html
      if (
        this.graph.isWrapping(state.cell) &&
        (this.bounds.width >= 2 || this.bounds.height >= 2) &&
        this.textarea.innerHTML != this.getEmptyLabelText()
      ) {
        this.textarea.style.wordWrap = mxConstants.WORD_WRAP;
        this.textarea.style.whiteSpace = 'normal';

        // Forces automatic reflow if text is removed from an oversize label and normal word wrap
        var tmp = Math.round(this.bounds.width / (document.documentMode == 8 ? scale : scale)) + this.wordWrapPadding;

        if (this.textarea.style.position != 'relative') {
          this.textarea.style.width = tmp + 'px';

          if (this.textarea.scrollWidth > tmp) {
            this.textarea.style.width = this.textarea.scrollWidth + 'px';
          }
        } else {
          this.textarea.style.maxWidth = tmp + 'px';
        }
      } else {
        // KNOWN: Trailing cursor in IE9 quirks mode is not visible
        this.textarea.style.whiteSpace = 'nowrap';
        this.textarea.style.width = '';
      }

      // LATER: Keep in visible area, add fine tuning for pixel precision
      // Workaround for wrong measuring in IE8 standards
      if (document.documentMode == 8) {
        this.textarea.style.zoom = '1';
        this.textarea.style.height = 'auto';
      }

      var ow = this.textarea.scrollWidth;
      var oh = this.textarea.scrollHeight;

      // TODO: Update CSS width and height if smaller than minResize or remove minResize
      //if (this.minResize != null)
      //{
      //	ow = Math.max(ow, this.minResize.width);
      //	oh = Math.max(oh, this.minResize.height);
      //}

      // LATER: Keep in visible area, add fine tuning for pixel precision
      if (document.documentMode == 8) {
        // LATER: Scaled wrapping and position is wrong in IE8
        this.textarea.style.left =
          Math.max(
            0,
            Math.ceil(
              (this.bounds.x - m.x * (this.bounds.width - (ow + 1) * scale) + ow * (scale - 1) * 0 + (m.x + 0.5) * 2) /
                scale
            )
          ) + 'px';
        this.textarea.style.top =
          Math.max(
            0,
            Math.ceil(
              (this.bounds.y -
                m.y * (this.bounds.height - (oh + 0.5) * scale) +
                oh * (scale - 1) * 0 +
                Math.abs(m.y + 0.5) * 1) /
                scale
            )
          ) + 'px';
        // Workaround for wrong event handling width and height
        this.textarea.style.width = Math.round(ow * scale) + 'px';
        this.textarea.style.height = Math.round(oh * scale) + 'px';
      } else if (mxClient.IS_QUIRKS) {
        this.textarea.style.left =
          Math.max(
            0,
            Math.ceil(
              this.bounds.x - m.x * (this.bounds.width - (ow + 1) * scale) + ow * (scale - 1) * 0 + (m.x + 0.5) * 2
            )
          ) + 'px';
        this.textarea.style.top =
          Math.max(
            0,
            Math.ceil(
              this.bounds.y -
                m.y * (this.bounds.height - (oh + 0.5) * scale) +
                oh * (scale - 1) * 0 +
                Math.abs(m.y + 0.5) * 1
            )
          ) + 'px';
      } else {
        this.textarea.style.left = Math.max(0, Math.round(this.bounds.x - m.x * (this.bounds.width - 2)) + 1) + 'px';
        this.textarea.style.top =
          Math.max(0, Math.round(this.bounds.y - m.y * (this.bounds.height - 4) + (m.y == -1 ? 3 : 0)) + 1) + 'px';
      }
    }

    if (mxClient.IS_VML) {
      this.textarea.style.zoom = scale;
    } else {
      mxUtils.setPrefixedStyle(this.textarea.style, 'transformOrigin', '0px 0px');
      mxUtils.setPrefixedStyle(
        this.textarea.style,
        'transform',
        'scale(' + scale + ',' + scale + ')' + (m == null ? '' : ' translate(' + m.x * 100 + '%,' + m.y * 100 + '%)')
      );
    }
  }
};

/**
 * Function: focusLost
 *
 * Called if the textarea has lost focus.
 */
mxCellEditor.prototype.focusLost = function () {
  this.stopEditing(!this.graph.isInvokesStopCellEditing());
};

/**
 * Function: getBackgroundColor
 *
 * Returns the background color for the in-place editor. This implementation
 * always returns null.
 */
mxCellEditor.prototype.getBackgroundColor = function (state) {
  return null;
};

/**
 * Function: isLegacyEditor
 *
 * Returns true if max-width is not supported or if the SVG root element in
 * in the graph does not have CSS position absolute. In these cases the text
 * editor must use CSS position absolute to avoid an offset but it will have
 * a less accurate line wrapping width during the text editing preview. This
 * implementation returns true for IE8- and quirks mode or if the CSS position
 * of the SVG element is not absolute.
 */
mxCellEditor.prototype.isLegacyEditor = function () {
  if (mxClient.IS_VML) {
    return true;
  } else {
    var absoluteRoot = false;

    if (mxClient.IS_SVG) {
      var root = this.graph.view.getDrawPane().ownerSVGElement;

      if (root != null) {
        var css = mxUtils.getCurrentStyle(root);

        if (css != null) {
          absoluteRoot = css.position == 'absolute';
        }
      }
    }

    return !absoluteRoot;
  }
};

/**
 * Function: startEditing
 *
 * Starts the editor for the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> to start editing.
 * trigger - Optional mouse event that triggered the editor.
 */
mxCellEditor.prototype.startEditing = function (cell, trigger) {
  this.stopEditing(true);
  this.align = null;

  // Creates new textarea instance
  if (this.textarea == null) {
    this.init();
  }

  if (this.graph.tooltipHandler != null) {
    this.graph.tooltipHandler.hideTooltip();
  }

  var state = this.graph.getView().getState(cell);

  if (state != null) {
    // Configures the style of the in-place editor
    var scale = this.graph.getView().scale;
    var size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
    var family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY);
    var color = mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, 'black');
    var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
    var bold =
      (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD;
    var italic =
      (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_ITALIC) ==
      mxConstants.FONT_ITALIC;
    var txtDecor = [];

    if (
      (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_UNDERLINE) ==
      mxConstants.FONT_UNDERLINE
    ) {
      txtDecor.push('underline');
    }

    if (
      (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_STRIKETHROUGH) ==
      mxConstants.FONT_STRIKETHROUGH
    ) {
      txtDecor.push('line-through');
    }

    this.textarea.style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT
      ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px'
      : mxConstants.LINE_HEIGHT;
    this.textarea.style.backgroundColor = this.getBackgroundColor(state);
    this.textarea.style.textDecoration = txtDecor.join(' ');
    this.textarea.style.fontWeight = bold ? 'bold' : 'normal';
    this.textarea.style.fontStyle = italic ? 'italic' : '';
    this.textarea.style.fontSize = Math.round(size) + 'px';
    this.textarea.style.zIndex = this.zIndex;
    this.textarea.style.fontFamily = family;
    this.textarea.style.textAlign = align;
    this.textarea.style.outline = 'none';
    this.textarea.style.color = color;

    var dir = (this.textDirection = mxUtils.getValue(
      state.style,
      mxConstants.STYLE_TEXT_DIRECTION,
      mxConstants.DEFAULT_TEXT_DIRECTION
    ));

    if (dir == mxConstants.TEXT_DIRECTION_AUTO) {
      if (
        state != null &&
        state.text != null &&
        state.text.dialect != mxConstants.DIALECT_STRICTHTML &&
        !mxUtils.isNode(state.text.value)
      ) {
        dir = state.text.getAutoDirection();
      }
    }

    if (dir == mxConstants.TEXT_DIRECTION_LTR || dir == mxConstants.TEXT_DIRECTION_RTL) {
      this.textarea.setAttribute('dir', dir);
    } else {
      this.textarea.removeAttribute('dir');
    }

    // Sets the initial editing value
    this.textarea.innerHTML = this.getInitialValue(state, trigger) || '';
    this.initialValue = this.textarea.innerHTML;

    // Uses an optional text value for empty labels which is cleared
    // when the first keystroke appears. This makes it easier to see
    // that a label is being edited even if the label is empty.
    if (this.textarea.innerHTML.length == 0 || this.textarea.innerHTML == '<br>') {
      this.textarea.innerHTML = this.getEmptyLabelText();
      this.clearOnChange = true;
    } else {
      this.clearOnChange = this.textarea.innerHTML == this.getEmptyLabelText();
    }

    this.graph.container.appendChild(this.textarea);

    // Update this after firing all potential events that could update the cleanOnChange flag
    this.editingCell = cell;
    this.trigger = trigger;
    this.textNode = null;

    if (state.text != null && this.isHideLabel(state)) {
      this.textNode = state.text.node;
      this.textNode.style.visibility = 'hidden';
    }

    // Workaround for initial offsetHeight not ready for heading in markup
    if (this.autoSize && (this.graph.model.isEdge(state.cell) || state.style[mxConstants.STYLE_OVERFLOW] != 'fill')) {
      window.setTimeout(
        mxUtils.bind(this, function () {
          this.resize();
        }),
        0
      );
    }

    this.resize();

    // Workaround for NS_ERROR_FAILURE in FF
    try {
      // Prefers blinking cursor over no selected text if empty
      this.textarea.focus();

      if (
        this.isSelectText() &&
        this.textarea.innerHTML.length > 0 &&
        (this.textarea.innerHTML != this.getEmptyLabelText() || !this.clearOnChange)
      ) {
        document.execCommand('selectAll', false, null);
      }
    } catch (e) {
      // ignore
    }
  }
};

/**
 * Function: isSelectText
 *
 * Returns <selectText>.
 */
mxCellEditor.prototype.isSelectText = function () {
  return this.selectText;
};

/**
 * Function: clearSelection
 *
 * Clears the selection.
 */
mxCellEditor.prototype.clearSelection = function () {
  var selection = null;

  if (window.getSelection) {
    selection = window.getSelection();
  } else if (document.selection) {
    selection = document.selection;
  }

  if (selection != null) {
    if (selection.empty) {
      selection.empty();
    } else if (selection.removeAllRanges) {
      selection.removeAllRanges();
    }
  }
};

/**
 * Function: stopEditing
 *
 * Stops the editor and applies the value if cancel is false.
 */
mxCellEditor.prototype.stopEditing = function (cancel) {
  cancel = cancel || false;

  if (this.editingCell != null) {
    if (this.textNode != null) {
      this.textNode.style.visibility = 'visible';
      this.textNode = null;
    }

    var state = !cancel ? this.graph.view.getState(this.editingCell) : null;

    var initial = this.initialValue;
    this.initialValue = null;
    this.editingCell = null;
    this.trigger = null;
    this.bounds = null;
    this.textarea.blur();
    this.clearSelection();

    if (this.textarea.parentNode != null) {
      this.textarea.parentNode.removeChild(this.textarea);
    }

    if (this.clearOnChange && this.textarea.innerHTML == this.getEmptyLabelText()) {
      this.textarea.innerHTML = '';
      this.clearOnChange = false;
    }

    if (state != null && (this.textarea.innerHTML != initial || this.align != null)) {
      this.prepareTextarea();
      var value = this.getCurrentValue(state);

      this.graph.getModel().beginUpdate();
      try {
        if (value != null) {
          this.applyValue(state, value);
        }

        if (this.align != null) {
          this.graph.setCellStyles(mxConstants.STYLE_ALIGN, this.align, [state.cell]);
        }
      } finally {
        this.graph.getModel().endUpdate();
      }
    }

    // Forces new instance on next edit for undo history reset
    mxEvent.release(this.textarea);
    this.textarea = null;
    this.align = null;
  }
};

/**
 * Function: prepareTextarea
 *
 * Prepares the textarea for getting its value in <stopEditing>.
 * This implementation removes the extra trailing linefeed in Firefox.
 */
mxCellEditor.prototype.prepareTextarea = function () {
  if (this.textarea.lastChild != null && this.textarea.lastChild.nodeName == 'BR') {
    this.textarea.removeChild(this.textarea.lastChild);
  }
};

/**
 * Function: isHideLabel
 *
 * Returns true if the label should be hidden while the cell is being
 * edited.
 */
mxCellEditor.prototype.isHideLabel = function (state) {
  return true;
};

/**
 * Function: getMinimumSize
 *
 * Returns the minimum width and height for editing the given state.
 */
mxCellEditor.prototype.getMinimumSize = function (state) {
  var scale = this.graph.getView().scale;

  return new mxRectangle(
    0,
    0,
    state.text == null ? 30 : state.text.size * scale + 20,
    this.textarea.style.textAlign == 'left' ? 120 : 40
  );
};

/**
 * Function: getEditorBounds
 *
 * Returns the <mxRectangle> that defines the bounds of the editor.
 */
mxCellEditor.prototype.getEditorBounds = function (state) {
  var isEdge = this.graph.getModel().isEdge(state.cell);
  var scale = this.graph.getView().scale;
  var minSize = this.getMinimumSize(state);
  var minWidth = minSize.width;
  var minHeight = minSize.height;
  var result = null;

  if (!isEdge && state.view.graph.cellRenderer.legacySpacing && state.style[mxConstants.STYLE_OVERFLOW] == 'fill') {
    result = state.shape.getLabelBounds(mxRectangle.fromRectangle(state));
  } else {
    var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 0) * scale;
    var spacingTop =
      (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0) + mxText.prototype.baseSpacingTop) * scale + spacing;
    var spacingRight =
      (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0) + mxText.prototype.baseSpacingRight) * scale +
      spacing;
    var spacingBottom =
      (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0) + mxText.prototype.baseSpacingBottom) * scale +
      spacing;
    var spacingLeft =
      (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0) + mxText.prototype.baseSpacingLeft) * scale + spacing;

    result = new mxRectangle(
      state.x,
      state.y,
      Math.max(minWidth, state.width - spacingLeft - spacingRight),
      Math.max(minHeight, state.height - spacingTop - spacingBottom)
    );
    var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
    var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);

    result =
      state.shape != null && hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE
        ? state.shape.getLabelBounds(result)
        : result;

    if (isEdge) {
      result.x = state.absoluteOffset.x;
      result.y = state.absoluteOffset.y;

      if (state.text != null && state.text.boundingBox != null) {
        // Workaround for label containing just spaces in which case
        // the bounding box location contains negative numbers
        if (state.text.boundingBox.x > 0) {
          result.x = state.text.boundingBox.x;
        }

        if (state.text.boundingBox.y > 0) {
          result.y = state.text.boundingBox.y;
        }
      }
    } else if (state.text != null && state.text.boundingBox != null) {
      result.x = Math.min(result.x, state.text.boundingBox.x);
      result.y = Math.min(result.y, state.text.boundingBox.y);
    }

    result.x += spacingLeft;
    result.y += spacingTop;

    if (state.text != null && state.text.boundingBox != null) {
      if (!isEdge) {
        result.width = Math.max(result.width, state.text.boundingBox.width);
        result.height = Math.max(result.height, state.text.boundingBox.height);
      } else {
        result.width = Math.max(minWidth, state.text.boundingBox.width);
        result.height = Math.max(minHeight, state.text.boundingBox.height);
      }
    }

    // Applies the horizontal and vertical label positions
    if (this.graph.getModel().isVertex(state.cell)) {
      var horizontal = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);

      if (horizontal == mxConstants.ALIGN_LEFT) {
        result.x -= state.width;
      } else if (horizontal == mxConstants.ALIGN_RIGHT) {
        result.x += state.width;
      }

      var vertical = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);

      if (vertical == mxConstants.ALIGN_TOP) {
        result.y -= state.height;
      } else if (vertical == mxConstants.ALIGN_BOTTOM) {
        result.y += state.height;
      }
    }
  }

  return new mxRectangle(
    Math.round(result.x),
    Math.round(result.y),
    Math.round(result.width),
    Math.round(result.height)
  );
};

/**
 * Function: getEmptyLabelText
 *
 * Returns the initial label value to be used of the label of the given
 * cell is empty. This label is displayed and cleared on the first keystroke.
 * This implementation returns <emptyLabelText>.
 *
 * Parameters:
 *
 * cell - <mxCell> for which a text for an empty editing box should be
 * returned.
 */
mxCellEditor.prototype.getEmptyLabelText = function (cell) {
  return this.emptyLabelText;
};

/**
 * Function: getEditingCell
 *
 * Returns the cell that is currently being edited or null if no cell is
 * being edited.
 */
mxCellEditor.prototype.getEditingCell = function () {
  return this.editingCell;
};

/**
 * Function: destroy
 *
 * Destroys the editor and removes all associated resources.
 */
mxCellEditor.prototype.destroy = function () {
  if (this.textarea != null) {
    mxEvent.release(this.textarea);

    if (this.textarea.parentNode != null) {
      this.textarea.parentNode.removeChild(this.textarea);
    }

    this.textarea = null;
  }

  if (this.changeHandler != null) {
    this.graph.getModel().removeListener(this.changeHandler);
    this.changeHandler = null;
  }

  if (this.zoomHandler) {
    this.graph.view.removeListener(this.zoomHandler);
    this.zoomHandler = null;
  }
};

/**
 * view/mxCellRenderer.js
 */
/**
 * Copyright (c) 2006-2017, JGraph Ltd
 * Copyright (c) 2006-2017, Gaudenz Alder
 */
/**
 * Class: mxCellRenderer
 *
 * Renders cells into a document object model. The <defaultShapes> is a global
 * map of shapename, constructor pairs that is used in all instances. You can
 * get a list of all available shape names using the following code.
 *
 * In general the cell renderer is in charge of creating, redrawing and
 * destroying the shape and label associated with a cell state, as well as
 * some other graphical objects, namely controls and overlays. The shape
 * hieararchy in the display (ie. the hierarchy in which the DOM nodes
 * appear in the document) does not reflect the cell hierarchy. The shapes
 * are a (flat) sequence of shapes and labels inside the draw pane of the
 * graph view, with some exceptions, namely the HTML labels being placed
 * directly inside the graph container for certain browsers.
 *
 * (code)
 * mxLog.show();
 * for (var i in mxCellRenderer.defaultShapes)
 * {
 *   mxLog.debug(i);
 * }
 * (end)
 *
 * Constructor: mxCellRenderer
 *
 * Constructs a new cell renderer with the following built-in shapes:
 * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,
 * swimlane, connector, actor and cloud.
 */
function mxCellRenderer() {}

/**
 * Variable: defaultShapes
 *
 * Static array that contains the globally registered shapes which are
 * known to all instances of this class. For adding new shapes you should
 * use the static <mxCellRenderer.registerShape> function.
 */
mxCellRenderer.defaultShapes = new Object();

/**
 * Variable: defaultEdgeShape
 *
 * Defines the default shape for edges. Default is <mxConnector>.
 */
mxCellRenderer.prototype.defaultEdgeShape = mxConnector;

/**
 * Variable: defaultVertexShape
 *
 * Defines the default shape for vertices. Default is <mxRectangleShape>.
 */
mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;

/**
 * Variable: defaultTextShape
 *
 * Defines the default shape for labels. Default is <mxText>.
 */
mxCellRenderer.prototype.defaultTextShape = mxText;

/**
 * Variable: legacyControlPosition
 *
 * Specifies if the folding icon should ignore the horizontal
 * orientation of a swimlane. Default is true.
 */
mxCellRenderer.prototype.legacyControlPosition = true;

/**
 * Variable: legacySpacing
 *
 * Specifies if spacing and label position should be ignored if overflow is
 * fill or width. Default is true for backwards compatiblity.
 */
mxCellRenderer.prototype.legacySpacing = true;

/**
 * Variable: antiAlias
 *
 * Anti-aliasing option for new shapes. Default is true.
 */
mxCellRenderer.prototype.antiAlias = true;

/**
 * Variable: minSvgStrokeWidth
 *
 * Minimum stroke width for SVG output.
 */
mxCellRenderer.prototype.minSvgStrokeWidth = 1;

/**
 * Variable: forceControlClickHandler
 *
 * Specifies if the enabled state of the graph should be ignored in the control
 * click handler (to allow folding in disabled graphs). Default is false.
 */
mxCellRenderer.prototype.forceControlClickHandler = false;

/**
 * Function: registerShape
 *
 * Registers the given constructor under the specified key in this instance
 * of the renderer.
 *
 * Example:
 *
 * (code)
 * mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
 * (end)
 *
 * Parameters:
 *
 * key - String representing the shape name.
 * shape - Constructor of the <mxShape> subclass.
 */
mxCellRenderer.registerShape = function (key, shape) {
  mxCellRenderer.defaultShapes[key] = shape;
};

// Adds default shapes into the default shapes array
mxCellRenderer.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
mxCellRenderer.registerShape(mxConstants.SHAPE_ELLIPSE, mxEllipse);
mxCellRenderer.registerShape(mxConstants.SHAPE_RHOMBUS, mxRhombus);
mxCellRenderer.registerShape(mxConstants.SHAPE_CYLINDER, mxCylinder);
mxCellRenderer.registerShape(mxConstants.SHAPE_CONNECTOR, mxConnector);
mxCellRenderer.registerShape(mxConstants.SHAPE_ACTOR, mxActor);
mxCellRenderer.registerShape(mxConstants.SHAPE_TRIANGLE, mxTriangle);
mxCellRenderer.registerShape(mxConstants.SHAPE_HEXAGON, mxHexagon);
mxCellRenderer.registerShape(mxConstants.SHAPE_CLOUD, mxCloud);
mxCellRenderer.registerShape(mxConstants.SHAPE_LINE, mxLine);
mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW, mxArrow);
mxCellRenderer.registerShape(mxConstants.SHAPE_ARROW_CONNECTOR, mxArrowConnector);
mxCellRenderer.registerShape(mxConstants.SHAPE_DOUBLE_ELLIPSE, mxDoubleEllipse);
mxCellRenderer.registerShape(mxConstants.SHAPE_SWIMLANE, mxSwimlane);
mxCellRenderer.registerShape(mxConstants.SHAPE_IMAGE, mxImageShape);
mxCellRenderer.registerShape(mxConstants.SHAPE_LABEL, mxLabel);

/**
 * Function: initializeShape
 *
 * Initializes the shape in the given state by calling its init method with
 * the correct container after configuring it using <configureShape>.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the shape should be initialized.
 */
mxCellRenderer.prototype.initializeShape = function (state) {
  state.shape.dialect = state.view.graph.dialect;
  this.configureShape(state);
  state.shape.init(state.view.getDrawPane());
};

/**
 * Function: createShape
 *
 * Creates and returns the shape for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the shape should be created.
 */
mxCellRenderer.prototype.createShape = function (state) {
  var shape = null;

  if (state.style != null) {
    // Checks if there is a stencil for the name and creates
    // a shape instance for the stencil if one exists
    var stencil = mxStencilRegistry.getStencil(state.style[mxConstants.STYLE_SHAPE]);

    if (stencil != null) {
      shape = new mxShape(stencil);
    } else {
      var ctor = this.getShapeConstructor(state);
      shape = new ctor();
    }
  }

  return shape;
};

/**
 * Function: createIndicatorShape
 *
 * Creates the indicator shape for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the indicator shape should be created.
 */
mxCellRenderer.prototype.createIndicatorShape = function (state) {
  state.shape.indicatorShape = this.getShape(state.view.graph.getIndicatorShape(state));
};

/**
 * Function: getShape
 *
 * Returns the shape for the given name from <defaultShapes>.
 */
mxCellRenderer.prototype.getShape = function (name) {
  return name != null ? mxCellRenderer.defaultShapes[name] : null;
};

/**
 * Function: getShapeConstructor
 *
 * Returns the constructor to be used for creating the shape.
 */
mxCellRenderer.prototype.getShapeConstructor = function (state) {
  var ctor = this.getShape(state.style[mxConstants.STYLE_SHAPE]);

  if (ctor == null) {
    ctor = state.view.graph.getModel().isEdge(state.cell) ? this.defaultEdgeShape : this.defaultVertexShape;
  }

  return ctor;
};

/**
 * Function: configureShape
 *
 * Configures the shape for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the shape should be configured.
 */
mxCellRenderer.prototype.configureShape = function (state) {
  state.shape.apply(state);
  state.shape.image = state.view.graph.getImage(state);
  state.shape.indicatorColor = state.view.graph.getIndicatorColor(state);
  state.shape.indicatorStrokeColor = state.style[mxConstants.STYLE_INDICATOR_STROKECOLOR];
  state.shape.indicatorGradientColor = state.view.graph.getIndicatorGradientColor(state);
  state.shape.indicatorDirection = state.style[mxConstants.STYLE_INDICATOR_DIRECTION];
  state.shape.indicatorImage = state.view.graph.getIndicatorImage(state);

  this.postConfigureShape(state);
};

/**
 * Function: postConfigureShape
 *
 * Replaces any reserved words used for attributes, eg. inherit,
 * indicated or swimlane for colors in the shape for the given state.
 * This implementation resolves these keywords on the fill, stroke
 * and gradient color keys.
 */
mxCellRenderer.prototype.postConfigureShape = function (state) {
  if (state.shape != null) {
    this.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);
    this.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);
    this.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);
    this.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);
    this.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);
  }
};

/**
 * Function: checkPlaceholderStyles
 *
 * Checks if the style of the given <mxCellState> contains 'inherit',
 * 'indicated' or 'swimlane' for colors that support those keywords.
 */
mxCellRenderer.prototype.checkPlaceholderStyles = function (state) {
  // LATER: Check if the color has actually changed
  if (state.style != null) {
    var values = ['inherit', 'swimlane', 'indicated'];
    var styles = [
      mxConstants.STYLE_FILLCOLOR,
      mxConstants.STYLE_STROKECOLOR,
      mxConstants.STYLE_GRADIENTCOLOR,
      mxConstants.STYLE_FONTCOLOR,
    ];

    for (var i = 0; i < styles.length; i++) {
      if (mxUtils.indexOf(values, state.style[styles[i]]) >= 0) {
        return true;
      }
    }
  }

  return false;
};

/**
 * Function: resolveColor
 *
 * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
 * the respective color on the shape.
 */
mxCellRenderer.prototype.resolveColor = function (state, field, key) {
  var shape = key == mxConstants.STYLE_FONTCOLOR ? state.text : state.shape;

  if (shape != null) {
    var graph = state.view.graph;
    var value = shape[field];
    var referenced = null;

    if (value == 'inherit') {
      referenced = graph.model.getParent(state.cell);
    } else if (value == 'swimlane') {
      shape[field] = key == mxConstants.STYLE_STROKECOLOR || key == mxConstants.STYLE_FONTCOLOR ? '#000000' : '#ffffff';

      if (graph.model.getTerminal(state.cell, false) != null) {
        referenced = graph.model.getTerminal(state.cell, false);
      } else {
        referenced = state.cell;
      }

      referenced = graph.getSwimlane(referenced);
      key = graph.swimlaneIndicatorColorAttribute;
    } else if (value == 'indicated' && state.shape != null) {
      shape[field] = state.shape.indicatorColor;
    } else if (key != mxConstants.STYLE_FILLCOLOR && value == mxConstants.STYLE_FILLCOLOR && state.shape != null) {
      shape[field] = state.style[mxConstants.STYLE_FILLCOLOR];
    } else if (key != mxConstants.STYLE_STROKECOLOR && value == mxConstants.STYLE_STROKECOLOR && state.shape != null) {
      shape[field] = state.style[mxConstants.STYLE_STROKECOLOR];
    }

    if (referenced != null) {
      var rstate = graph.getView().getState(referenced);
      shape[field] = null;

      if (rstate != null) {
        var rshape = key == mxConstants.STYLE_FONTCOLOR ? rstate.text : rstate.shape;

        if (rshape != null && field != 'indicatorColor') {
          shape[field] = rshape[field];
        } else {
          shape[field] = rstate.style[key];
        }
      }
    }
  }
};

/**
 * Function: getLabelValue
 *
 * Returns the value to be used for the label.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the label should be created.
 */
mxCellRenderer.prototype.getLabelValue = function (state) {
  return state.view.graph.getLabel(state.cell);
};

/**
 * Function: createLabel
 *
 * Creates the label for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the label should be created.
 */
mxCellRenderer.prototype.createLabel = function (state, value) {
  var graph = state.view.graph;
  var isEdge = graph.getModel().isEdge(state.cell);

  if (state.style[mxConstants.STYLE_FONTSIZE] > 0 || state.style[mxConstants.STYLE_FONTSIZE] == null) {
    // Avoids using DOM node for empty labels
    var isForceHtml = graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value));

    state.text = new this.defaultTextShape(
      value,
      new mxRectangle(),
      state.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER,
      graph.getVerticalAlign(state),
      state.style[mxConstants.STYLE_FONTCOLOR],
      state.style[mxConstants.STYLE_FONTFAMILY],
      state.style[mxConstants.STYLE_FONTSIZE],
      state.style[mxConstants.STYLE_FONTSTYLE],
      state.style[mxConstants.STYLE_SPACING],
      state.style[mxConstants.STYLE_SPACING_TOP],
      state.style[mxConstants.STYLE_SPACING_RIGHT],
      state.style[mxConstants.STYLE_SPACING_BOTTOM],
      state.style[mxConstants.STYLE_SPACING_LEFT],
      state.style[mxConstants.STYLE_HORIZONTAL],
      state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
      state.style[mxConstants.STYLE_LABEL_BORDERCOLOR],
      graph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),
      graph.isLabelClipped(state.cell),
      state.style[mxConstants.STYLE_OVERFLOW],
      state.style[mxConstants.STYLE_LABEL_PADDING],
      mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION)
    );
    state.text.opacity = mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_OPACITY, 100);
    state.text.dialect = isForceHtml ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
    state.text.style = state.style;
    state.text.state = state;
    this.initializeLabel(state, state.text);

    // Workaround for touch devices routing all events for a mouse gesture
    // (down, move, up) via the initial DOM node. IE additionally redirects
    // the event via the initial DOM node but the event source is the node
    // under the mouse, so we need to check if this is the case and force
    // getCellAt for the subsequent mouseMoves and the final mouseUp.
    var forceGetCell = false;

    var getState = function (evt) {
      var result = state;

      if (mxClient.IS_TOUCH || forceGetCell) {
        var x = mxEvent.getClientX(evt);
        var y = mxEvent.getClientY(evt);

        // Dispatches the drop event to the graph which
        // consumes and executes the source function
        var pt = mxUtils.convertPoint(graph.container, x, y);
        result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
      }

      return result;
    };

    // TODO: Add handling for special touch device gestures
    mxEvent.addGestureListeners(
      state.text.node,
      mxUtils.bind(this, function (evt) {
        if (this.isLabelEvent(state, evt)) {
          graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
          forceGetCell = graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG';
        }
      }),
      mxUtils.bind(this, function (evt) {
        if (this.isLabelEvent(state, evt)) {
          graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
        }
      }),
      mxUtils.bind(this, function (evt) {
        if (this.isLabelEvent(state, evt)) {
          graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
          forceGetCell = false;
        }
      })
    );

    // Uses double click timeout in mxGraph for quirks mode
    if (graph.nativeDblClickEnabled) {
      mxEvent.addListener(
        state.text.node,
        'dblclick',
        mxUtils.bind(this, function (evt) {
          if (this.isLabelEvent(state, evt)) {
            graph.dblClick(evt, state.cell);
            mxEvent.consume(evt);
          }
        })
      );
    }
  }
};

/**
 * Function: initializeLabel
 *
 * Initiailzes the label with a suitable container.
 *
 * Parameters:
 *
 * state - <mxCellState> whose label should be initialized.
 */
mxCellRenderer.prototype.initializeLabel = function (state, shape) {
  if (mxClient.IS_SVG && mxClient.NO_FO && shape.dialect != mxConstants.DIALECT_SVG) {
    shape.init(state.view.graph.container);
  } else {
    shape.init(state.view.getDrawPane());
  }
};

/**
 * Function: createCellOverlays
 *
 * Creates the actual shape for showing the overlay for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the overlay should be created.
 */
mxCellRenderer.prototype.createCellOverlays = function (state) {
  var graph = state.view.graph;
  var overlays = graph.getCellOverlays(state.cell);
  var dict = null;

  if (overlays != null) {
    dict = new mxDictionary();

    for (var i = 0; i < overlays.length; i++) {
      var shape = state.overlays != null ? state.overlays.remove(overlays[i]) : null;

      if (shape == null) {
        var tmp = new mxImageShape(new mxRectangle(), overlays[i].image.src);
        tmp.dialect = state.view.graph.dialect;
        tmp.preserveImageAspect = false;
        tmp.overlay = overlays[i];
        this.initializeOverlay(state, tmp);
        this.installCellOverlayListeners(state, overlays[i], tmp);

        if (overlays[i].cursor != null) {
          tmp.node.style.cursor = overlays[i].cursor;
        }

        dict.put(overlays[i], tmp);
      } else {
        dict.put(overlays[i], shape);
      }
    }
  }

  // Removes unused
  if (state.overlays != null) {
    state.overlays.visit(function (id, shape) {
      shape.destroy();
    });
  }

  state.overlays = dict;
};

/**
 * Function: initializeOverlay
 *
 * Initializes the given overlay.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the overlay should be created.
 * overlay - <mxImageShape> that represents the overlay.
 */
mxCellRenderer.prototype.initializeOverlay = function (state, overlay) {
  overlay.init(state.view.getOverlayPane());
};

/**
 * Function: installOverlayListeners
 *
 * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
 * <mxShape> that represents the overlay.
 */
mxCellRenderer.prototype.installCellOverlayListeners = function (state, overlay, shape) {
  var graph = state.view.graph;

  mxEvent.addListener(shape.node, 'click', function (evt) {
    if (graph.isEditing()) {
      graph.stopEditing(!graph.isInvokesStopCellEditing());
    }

    overlay.fireEvent(new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', state.cell));
  });

  mxEvent.addGestureListeners(
    shape.node,
    function (evt) {
      mxEvent.consume(evt);
    },
    function (evt) {
      graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
    }
  );

  if (mxClient.IS_TOUCH) {
    mxEvent.addListener(shape.node, 'touchend', function (evt) {
      overlay.fireEvent(new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', state.cell));
    });
  }
};

/**
 * Function: createControl
 *
 * Creates the control for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the control should be created.
 */
mxCellRenderer.prototype.createControl = function (state) {
  var graph = state.view.graph;
  var image = graph.getFoldingImage(state);

  if (graph.foldingEnabled && image != null) {
    if (state.control == null) {
      var b = new mxRectangle(0, 0, image.width, image.height);
      state.control = new mxImageShape(b, image.src);
      state.control.preserveImageAspect = false;
      state.control.dialect = graph.dialect;

      this.initControl(state, state.control, true, this.createControlClickHandler(state));
    }
  } else if (state.control != null) {
    state.control.destroy();
    state.control = null;
  }
};

/**
 * Function: createControlClickHandler
 *
 * Hook for creating the click handler for the folding icon.
 *
 * Parameters:
 *
 * state - <mxCellState> whose control click handler should be returned.
 */
mxCellRenderer.prototype.createControlClickHandler = function (state) {
  var graph = state.view.graph;

  return mxUtils.bind(this, function (evt) {
    if (this.forceControlClickHandler || graph.isEnabled()) {
      var collapse = !graph.isCellCollapsed(state.cell);
      graph.foldCells(collapse, false, [state.cell], null, evt);
      mxEvent.consume(evt);
    }
  });
};

/**
 * Function: initControl
 *
 * Initializes the given control and returns the corresponding DOM node.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the control should be initialized.
 * control - <mxShape> to be initialized.
 * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.
 * clickHandler - Optional function to implement clicks on the control.
 */
mxCellRenderer.prototype.initControl = function (state, control, handleEvents, clickHandler) {
  var graph = state.view.graph;

  // In the special case where the label is in HTML and the display is SVG the image
  // should go into the graph container directly in order to be clickable. Otherwise
  // it is obscured by the HTML label that overlaps the cell.
  var isForceHtml = graph.isHtmlLabel(state.cell) && mxClient.NO_FO && graph.dialect == mxConstants.DIALECT_SVG;

  if (isForceHtml) {
    control.dialect = mxConstants.DIALECT_PREFERHTML;
    control.init(graph.container);
    control.node.style.zIndex = 1;
  } else {
    control.init(state.view.getOverlayPane());
  }

  var node = control.innerNode || control.node;

  // Workaround for missing click event on iOS is to check tolerance below
  if (clickHandler != null && !mxClient.IS_IOS) {
    if (graph.isEnabled()) {
      node.style.cursor = 'pointer';
    }

    mxEvent.addListener(node, 'click', clickHandler);
  }

  if (handleEvents) {
    var first = null;

    mxEvent.addGestureListeners(
      node,
      function (evt) {
        first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
        graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
        mxEvent.consume(evt);
      },
      function (evt) {
        graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
      },
      function (evt) {
        graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, state));
        mxEvent.consume(evt);
      }
    );

    // Uses capture phase for event interception to stop bubble phase
    if (clickHandler != null && mxClient.IS_IOS) {
      node.addEventListener(
        'touchend',
        function (evt) {
          if (first != null) {
            var tol = graph.tolerance;

            if (
              Math.abs(first.x - mxEvent.getClientX(evt)) < tol &&
              Math.abs(first.y - mxEvent.getClientY(evt)) < tol
            ) {
              clickHandler.call(clickHandler, evt);
              mxEvent.consume(evt);
            }
          }
        },
        true
      );
    }
  }

  return node;
};

/**
 * Function: isShapeEvent
 *
 * Returns true if the event is for the shape of the given state. This
 * implementation always returns true.
 *
 * Parameters:
 *
 * state - <mxCellState> whose shape fired the event.
 * evt - Mouse event which was fired.
 */
mxCellRenderer.prototype.isShapeEvent = function (state, evt) {
  return true;
};

/**
 * Function: isLabelEvent
 *
 * Returns true if the event is for the label of the given state. This
 * implementation always returns true.
 *
 * Parameters:
 *
 * state - <mxCellState> whose label fired the event.
 * evt - Mouse event which was fired.
 */
mxCellRenderer.prototype.isLabelEvent = function (state, evt) {
  return true;
};

/**
 * Function: installListeners
 *
 * Installs the event listeners for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the event listeners should be isntalled.
 */
mxCellRenderer.prototype.installListeners = function (state) {
  var graph = state.view.graph;

  // Workaround for touch devices routing all events for a mouse
  // gesture (down, move, up) via the initial DOM node. Same for
  // HTML images in all IE versions (VML images are working).
  var getState = function (evt) {
    var result = state;

    if ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH) {
      var x = mxEvent.getClientX(evt);
      var y = mxEvent.getClientY(evt);

      // Dispatches the drop event to the graph which
      // consumes and executes the source function
      var pt = mxUtils.convertPoint(graph.container, x, y);
      result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
    }

    return result;
  };

  mxEvent.addGestureListeners(
    state.shape.node,
    mxUtils.bind(this, function (evt) {
      if (this.isShapeEvent(state, evt)) {
        graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
      }
    }),
    mxUtils.bind(this, function (evt) {
      if (this.isShapeEvent(state, evt)) {
        graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
      }
    }),
    mxUtils.bind(this, function (evt) {
      if (this.isShapeEvent(state, evt)) {
        graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
      }
    })
  );

  // Uses double click timeout in mxGraph for quirks mode
  if (graph.nativeDblClickEnabled) {
    mxEvent.addListener(
      state.shape.node,
      'dblclick',
      mxUtils.bind(this, function (evt) {
        if (this.isShapeEvent(state, evt)) {
          graph.dblClick(evt, state.cell);
          mxEvent.consume(evt);
        }
      })
    );
  }
};

/**
 * Function: redrawLabel
 *
 * Redraws the label for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> whose label should be redrawn.
 */
mxCellRenderer.prototype.redrawLabel = function (state, forced) {
  var graph = state.view.graph;
  var value = this.getLabelValue(state);
  var wrapping = graph.isWrapping(state.cell);
  var clipping = graph.isLabelClipped(state.cell);
  var isForceHtml = state.view.graph.isHtmlLabel(state.cell) || (value != null && mxUtils.isNode(value));
  var dialect = isForceHtml ? mxConstants.DIALECT_STRICTHTML : state.view.graph.dialect;
  var overflow = state.style[mxConstants.STYLE_OVERFLOW] || 'visible';

  if (
    state.text != null &&
    (state.text.wrap != wrapping ||
      state.text.clipped != clipping ||
      state.text.overflow != overflow ||
      state.text.dialect != dialect)
  ) {
    state.text.destroy();
    state.text = null;
  }

  if (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0)) {
    this.createLabel(state, value);
  } else if (state.text != null && (value == null || value.length == 0)) {
    state.text.destroy();
    state.text = null;
  }

  if (state.text != null) {
    // Forced is true if the style has changed, so to get the updated
    // result in getLabelBounds we apply the new style to the shape
    if (forced) {
      // Checks if a full repaint is needed
      if (state.text.lastValue != null && this.isTextShapeInvalid(state, state.text)) {
        // Forces a full repaint
        state.text.lastValue = null;
      }

      state.text.resetStyles();
      state.text.apply(state);

      // Special case where value is obtained via hook in graph
      state.text.valign = graph.getVerticalAlign(state);
    }

    var bounds = this.getLabelBounds(state);
    var nextScale = this.getTextScale(state);
    this.resolveColor(state, 'color', mxConstants.STYLE_FONTCOLOR);

    if (
      forced ||
      state.text.value != value ||
      state.text.isWrapping != wrapping ||
      state.text.overflow != overflow ||
      state.text.isClipping != clipping ||
      state.text.scale != nextScale ||
      state.text.dialect != dialect ||
      state.text.bounds == null ||
      !state.text.bounds.equals(bounds)
    ) {
      state.text.dialect = dialect;
      state.text.value = value;
      state.text.bounds = bounds;
      state.text.scale = nextScale;
      state.text.wrap = wrapping;
      state.text.clipped = clipping;
      state.text.overflow = overflow;

      // Preserves visible state
      var vis = state.text.node.style.visibility;
      this.redrawLabelShape(state.text);
      state.text.node.style.visibility = vis;
    }
  }
};

/**
 * Function: isTextShapeInvalid
 *
 * Returns true if the style for the text shape has changed.
 *
 * Parameters:
 *
 * state - <mxCellState> whose label should be checked.
 * shape - <mxText> shape to be checked.
 */
mxCellRenderer.prototype.isTextShapeInvalid = function (state, shape) {
  function check(property, stylename, defaultValue) {
    var result = false;

    // Workaround for spacing added to directional spacing
    if (
      stylename == 'spacingTop' ||
      stylename == 'spacingRight' ||
      stylename == 'spacingBottom' ||
      stylename == 'spacingLeft'
    ) {
      result = parseFloat(shape[property]) - parseFloat(shape.spacing) != (state.style[stylename] || defaultValue);
    } else {
      result = shape[property] != (state.style[stylename] || defaultValue);
    }

    return result;
  }

  return (
    check('fontStyle', mxConstants.STYLE_FONTSTYLE, mxConstants.DEFAULT_FONTSTYLE) ||
    check('family', mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY) ||
    check('size', mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) ||
    check('color', mxConstants.STYLE_FONTCOLOR, 'black') ||
    check('align', mxConstants.STYLE_ALIGN, '') ||
    check('valign', mxConstants.STYLE_VERTICAL_ALIGN, '') ||
    check('spacing', mxConstants.STYLE_SPACING, 2) ||
    check('spacingTop', mxConstants.STYLE_SPACING_TOP, 0) ||
    check('spacingRight', mxConstants.STYLE_SPACING_RIGHT, 0) ||
    check('spacingBottom', mxConstants.STYLE_SPACING_BOTTOM, 0) ||
    check('spacingLeft', mxConstants.STYLE_SPACING_LEFT, 0) ||
    check('horizontal', mxConstants.STYLE_HORIZONTAL, true) ||
    check('background', mxConstants.STYLE_LABEL_BACKGROUNDCOLOR) ||
    check('border', mxConstants.STYLE_LABEL_BORDERCOLOR) ||
    check('opacity', mxConstants.STYLE_TEXT_OPACITY, 100) ||
    check('textDirection', mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION)
  );
};

/**
 * Function: redrawLabelShape
 *
 * Called to invoked redraw on the given text shape.
 *
 * Parameters:
 *
 * shape - <mxText> shape to be redrawn.
 */
mxCellRenderer.prototype.redrawLabelShape = function (shape) {
  shape.redraw();
};

/**
 * Function: getTextScale
 *
 * Returns the scaling used for the label of the given state
 *
 * Parameters:
 *
 * state - <mxCellState> whose label scale should be returned.
 */
mxCellRenderer.prototype.getTextScale = function (state) {
  return state.view.scale;
};

/**
 * Function: getLabelBounds
 *
 * Returns the bounds to be used to draw the label of the given state.
 *
 * Parameters:
 *
 * state - <mxCellState> whose label bounds should be returned.
 */
mxCellRenderer.prototype.getLabelBounds = function (state) {
  var graph = state.view.graph;
  var scale = state.view.scale;
  var isEdge = graph.getModel().isEdge(state.cell);
  var bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);

  if (isEdge) {
    var spacing = state.text.getSpacing();
    bounds.x += spacing.x * scale;
    bounds.y += spacing.y * scale;

    var geo = graph.getCellGeometry(state.cell);

    if (geo != null) {
      bounds.width = Math.max(0, geo.width * scale);
      bounds.height = Math.max(0, geo.height * scale);
    }
  } else {
    // Inverts label position
    if (state.text.isPaintBoundsInverted()) {
      var tmp = bounds.x;
      bounds.x = bounds.y;
      bounds.y = tmp;
    }

    bounds.x += state.x;
    bounds.y += state.y;

    // Minimum of 1 fixes alignment bug in HTML labels
    bounds.width = Math.max(1, state.width);
    bounds.height = Math.max(1, state.height);
  }

  if (state.text.isPaintBoundsInverted()) {
    // Rotates around center of state
    var t = (state.width - state.height) / 2;
    bounds.x += t;
    bounds.y -= t;
    var tmp = bounds.width;
    bounds.width = bounds.height;
    bounds.height = tmp;
  }

  // Shape can modify its label bounds
  if (state.shape != null) {
    var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
    var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);

    if (hpos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_MIDDLE) {
      bounds = state.shape.getLabelBounds(bounds);
    }
  }

  // Label width style overrides actual label width
  var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);

  if (lw != null) {
    bounds.width = parseFloat(lw) * scale;
  }

  if (!isEdge) {
    this.rotateLabelBounds(state, bounds);
  }

  return bounds;
};

/**
 * Function: rotateLabelBounds
 *
 * Adds the shape rotation to the given label bounds and
 * applies the alignment and offsets.
 *
 * Parameters:
 *
 * state - <mxCellState> whose label bounds should be rotated.
 * bounds - <mxRectangle> the rectangle to be rotated.
 */
mxCellRenderer.prototype.rotateLabelBounds = function (state, bounds) {
  bounds.y -= state.text.margin.y * bounds.height;
  bounds.x -= state.text.margin.x * bounds.width;

  if (
    !this.legacySpacing ||
    (state.style[mxConstants.STYLE_OVERFLOW] != 'fill' && state.style[mxConstants.STYLE_OVERFLOW] != 'width')
  ) {
    var s = state.view.scale;
    var spacing = state.text.getSpacing();
    bounds.x += spacing.x * s;
    bounds.y += spacing.y * s;

    var hpos = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
    var vpos = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
    var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);

    bounds.width = Math.max(
      0,
      bounds.width -
        (hpos == mxConstants.ALIGN_CENTER && lw == null ? state.text.spacingLeft * s + state.text.spacingRight * s : 0)
    );
    bounds.height = Math.max(
      0,
      bounds.height - (vpos == mxConstants.ALIGN_MIDDLE ? state.text.spacingTop * s + state.text.spacingBottom * s : 0)
    );
  }

  var theta = state.text.getTextRotation();

  // Only needed if rotated around another center
  if (theta != 0 && state != null && state.view.graph.model.isVertex(state.cell)) {
    var cx = state.getCenterX();
    var cy = state.getCenterY();

    if (bounds.x != cx || bounds.y != cy) {
      var rad = theta * (Math.PI / 180);
      var pt = mxUtils.getRotatedPoint(
        new mxPoint(bounds.x, bounds.y),
        Math.cos(rad),
        Math.sin(rad),
        new mxPoint(cx, cy)
      );

      bounds.x = pt.x;
      bounds.y = pt.y;
    }
  }
};

/**
 * Function: redrawCellOverlays
 *
 * Redraws the overlays for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> whose overlays should be redrawn.
 */
mxCellRenderer.prototype.redrawCellOverlays = function (state, forced) {
  this.createCellOverlays(state);

  if (state.overlays != null) {
    var rot = mxUtils.mod(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0), 90);
    var rad = mxUtils.toRadians(rot);
    var cos = Math.cos(rad);
    var sin = Math.sin(rad);

    state.overlays.visit(function (id, shape) {
      var bounds = shape.overlay.getBounds(state);

      if (!state.view.graph.getModel().isEdge(state.cell)) {
        if (state.shape != null && rot != 0) {
          var cx = bounds.getCenterX();
          var cy = bounds.getCenterY();

          var point = mxUtils.getRotatedPoint(
            new mxPoint(cx, cy),
            cos,
            sin,
            new mxPoint(state.getCenterX(), state.getCenterY())
          );

          cx = point.x;
          cy = point.y;
          bounds.x = Math.round(cx - bounds.width / 2);
          bounds.y = Math.round(cy - bounds.height / 2);
        }
      }

      if (forced || shape.bounds == null || shape.scale != state.view.scale || !shape.bounds.equals(bounds)) {
        shape.bounds = bounds;
        shape.scale = state.view.scale;
        shape.redraw();
      }
    });
  }
};

/**
 * Function: redrawControl
 *
 * Redraws the control for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> whose control should be redrawn.
 */
mxCellRenderer.prototype.redrawControl = function (state, forced) {
  var image = state.view.graph.getFoldingImage(state);

  if (state.control != null && image != null) {
    var bounds = this.getControlBounds(state, image.width, image.height);
    var r = this.legacyControlPosition
      ? mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0)
      : state.shape.getTextRotation();
    var s = state.view.scale;

    if (forced || state.control.scale != s || !state.control.bounds.equals(bounds) || state.control.rotation != r) {
      state.control.rotation = r;
      state.control.bounds = bounds;
      state.control.scale = s;

      state.control.redraw();
    }
  }
};

/**
 * Function: getControlBounds
 *
 * Returns the bounds to be used to draw the control (folding icon) of the
 * given state.
 */
mxCellRenderer.prototype.getControlBounds = function (state, w, h) {
  if (state.control != null) {
    var s = state.view.scale;
    var cx = state.getCenterX();
    var cy = state.getCenterY();

    if (!state.view.graph.getModel().isEdge(state.cell)) {
      cx = state.x + w * s;
      cy = state.y + h * s;

      if (state.shape != null) {
        // TODO: Factor out common code
        var rot = state.shape.getShapeRotation();

        if (this.legacyControlPosition) {
          rot = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);
        } else {
          if (state.shape.isPaintBoundsInverted()) {
            var t = (state.width - state.height) / 2;
            cx += t;
            cy -= t;
          }
        }

        if (rot != 0) {
          var rad = mxUtils.toRadians(rot);
          var cos = Math.cos(rad);
          var sin = Math.sin(rad);

          var point = mxUtils.getRotatedPoint(
            new mxPoint(cx, cy),
            cos,
            sin,
            new mxPoint(state.getCenterX(), state.getCenterY())
          );
          cx = point.x;
          cy = point.y;
        }
      }
    }

    return state.view.graph.getModel().isEdge(state.cell)
      ? new mxRectangle(
          Math.round(cx - (w / 2) * s),
          Math.round(cy - (h / 2) * s),
          Math.round(w * s),
          Math.round(h * s)
        )
      : new mxRectangle(
          Math.round(cx - (w / 2) * s),
          Math.round(cy - (h / 2) * s),
          Math.round(w * s),
          Math.round(h * s)
        );
  }

  return null;
};

/**
 * Function: insertStateAfter
 *
 * Inserts the given array of <mxShapes> after the given nodes in the DOM.
 *
 * Parameters:
 *
 * shapes - Array of <mxShapes> to be inserted.
 * node - Node in <drawPane> after which the shapes should be inserted.
 * htmlNode - Node in the graph container after which the shapes should be inserted that
 * will not go into the <drawPane> (eg. HTML labels without foreignObjects).
 */
mxCellRenderer.prototype.insertStateAfter = function (state, node, htmlNode) {
  var shapes = this.getShapesForState(state);

  for (var i = 0; i < shapes.length; i++) {
    if (shapes[i] != null && shapes[i].node != null) {
      var html =
        shapes[i].node.parentNode != state.view.getDrawPane() &&
        shapes[i].node.parentNode != state.view.getOverlayPane();
      var temp = html ? htmlNode : node;

      if (temp != null && temp.nextSibling != shapes[i].node) {
        if (temp.nextSibling == null) {
          temp.parentNode.appendChild(shapes[i].node);
        } else {
          temp.parentNode.insertBefore(shapes[i].node, temp.nextSibling);
        }
      } else if (temp == null) {
        // Special case: First HTML node should be first sibling after canvas
        if (shapes[i].node.parentNode == state.view.graph.container) {
          var canvas = state.view.canvas;

          while (canvas != null && canvas.parentNode != state.view.graph.container) {
            canvas = canvas.parentNode;
          }

          if (canvas != null && canvas.nextSibling != null) {
            if (canvas.nextSibling != shapes[i].node) {
              shapes[i].node.parentNode.insertBefore(shapes[i].node, canvas.nextSibling);
            }
          } else {
            shapes[i].node.parentNode.appendChild(shapes[i].node);
          }
        } else if (
          shapes[i].node.parentNode != null &&
          shapes[i].node.parentNode.firstChild != null &&
          shapes[i].node.parentNode.firstChild != shapes[i].node
        ) {
          // Inserts the node as the first child of the parent to implement the order
          shapes[i].node.parentNode.insertBefore(shapes[i].node, shapes[i].node.parentNode.firstChild);
        }
      }

      if (html) {
        htmlNode = shapes[i].node;
      } else {
        node = shapes[i].node;
      }
    }
  }

  return [node, htmlNode];
};

/**
 * Function: getShapesForState
 *
 * Returns the <mxShapes> for the given cell state in the order in which they should
 * appear in the DOM.
 *
 * Parameters:
 *
 * state - <mxCellState> whose shapes should be returned.
 */
mxCellRenderer.prototype.getShapesForState = function (state) {
  return [state.shape, state.text, state.control];
};

/**
 * Function: redraw
 *
 * Updates the bounds or points and scale of the shapes for the given cell
 * state. This is called in mxGraphView.validatePoints as the last step of
 * updating all cells.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the shapes should be updated.
 * force - Optional boolean that specifies if the cell should be reconfiured
 * and redrawn without any additional checks.
 * rendering - Optional boolean that specifies if the cell should actually
 * be drawn into the DOM. If this is false then redraw and/or reconfigure
 * will not be called on the shape.
 */
mxCellRenderer.prototype.redraw = function (state, force, rendering) {
  var shapeChanged = this.redrawShape(state, force, rendering);

  if (state.shape != null && (rendering == null || rendering)) {
    this.redrawLabel(state, shapeChanged);
    this.redrawCellOverlays(state, shapeChanged);
    this.redrawControl(state, shapeChanged);
  }
};

/**
 * Function: redrawShape
 *
 * Redraws the shape for the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> whose label should be redrawn.
 */
mxCellRenderer.prototype.redrawShape = function (state, force, rendering) {
  var model = state.view.graph.model;
  var shapeChanged = false;

  // Forces creation of new shape if shape style has changed
  if (
    state.shape != null &&
    state.shape.style != null &&
    state.style != null &&
    state.shape.style[mxConstants.STYLE_SHAPE] != state.style[mxConstants.STYLE_SHAPE]
  ) {
    state.shape.destroy();
    state.shape = null;
  }

  if (
    state.shape == null &&
    state.view.graph.container != null &&
    state.cell != state.view.currentRoot &&
    (model.isVertex(state.cell) || model.isEdge(state.cell))
  ) {
    state.shape = this.createShape(state);

    if (state.shape != null) {
      state.shape.minSvgStrokeWidth = this.minSvgStrokeWidth;
      state.shape.antiAlias = this.antiAlias;

      this.createIndicatorShape(state);
      this.initializeShape(state);
      this.createCellOverlays(state);
      this.installListeners(state);

      // Forces a refresh of the handler if one exists
      state.view.graph.selectionCellsHandler.updateHandler(state);
    }
  } else if (
    !force &&
    state.shape != null &&
    (!mxUtils.equalEntries(state.shape.style, state.style) || this.checkPlaceholderStyles(state))
  ) {
    state.shape.resetStyles();
    this.configureShape(state);
    // LATER: Ignore update for realtime to fix reset of current gesture
    state.view.graph.selectionCellsHandler.updateHandler(state);
    force = true;
  }

  // Updates indicator shape
  if (state.shape != null && state.shape.indicatorShape != this.getShape(state.view.graph.getIndicatorShape(state))) {
    if (state.shape.indicator != null) {
      state.shape.indicator.destroy();
      state.shape.indicator = null;
    }

    this.createIndicatorShape(state);

    if (state.shape.indicatorShape != null) {
      state.shape.indicator = new state.shape.indicatorShape();
      state.shape.indicator.dialect = state.shape.dialect;
      state.shape.indicator.init(state.node);
      force = true;
    }
  }

  if (state.shape != null) {
    // Handles changes of the collapse icon
    this.createControl(state);

    // Redraws the cell if required, ignores changes to bounds if points are
    // defined as the bounds are updated for the given points inside the shape
    if (force || this.isShapeInvalid(state, state.shape)) {
      if (state.absolutePoints != null) {
        state.shape.points = state.absolutePoints.slice();
        state.shape.bounds = null;
      } else {
        state.shape.points = null;
        state.shape.bounds = new mxRectangle(state.x, state.y, state.width, state.height);
      }

      state.shape.scale = state.view.scale;

      if (rendering == null || rendering) {
        this.doRedrawShape(state);
      } else {
        state.shape.updateBoundingBox();
      }

      shapeChanged = true;
    }
  }

  return shapeChanged;
};

/**
 * Function: doRedrawShape
 *
 * Invokes redraw on the shape of the given state.
 */
mxCellRenderer.prototype.doRedrawShape = function (state) {
  state.shape.redraw();
};

/**
 * Function: isShapeInvalid
 *
 * Returns true if the given shape must be repainted.
 */
mxCellRenderer.prototype.isShapeInvalid = function (state, shape) {
  return (
    shape.bounds == null ||
    shape.scale != state.view.scale ||
    (state.absolutePoints == null && !shape.bounds.equals(state)) ||
    (state.absolutePoints != null && !mxUtils.equalPoints(shape.points, state.absolutePoints))
  );
};

/**
 * Function: destroy
 *
 * Destroys the shapes associated with the given cell state.
 *
 * Parameters:
 *
 * state - <mxCellState> for which the shapes should be destroyed.
 */
mxCellRenderer.prototype.destroy = function (state) {
  if (state.shape != null) {
    if (state.text != null) {
      state.text.destroy();
      state.text = null;
    }

    if (state.overlays != null) {
      state.overlays.visit(function (id, shape) {
        shape.destroy();
      });

      state.overlays = null;
    }

    if (state.control != null) {
      state.control.destroy();
      state.control = null;
    }

    state.shape.destroy();
    state.shape = null;
  }
};

/**
 * view/mxEdgeStyle.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxEdgeStyle = {
  /**
   * Class: mxEdgeStyle
   *
   * Provides various edge styles to be used as the values for
   * <mxConstants.STYLE_EDGE> in a cell style.
   *
   * Example:
   *
   * (code)
   * var style = stylesheet.getDefaultEdgeStyle();
   * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
   * (end)
   *
   * Sets the default edge style to <ElbowConnector>.
   *
   * Custom edge style:
   *
   * To write a custom edge style, a function must be added to the mxEdgeStyle
   * object as follows:
   *
   * (code)
   * mxEdgeStyle.MyStyle = function(state, source, target, points, result)
   * {
   *   if (source != null && target != null)
   *   {
   *     var pt = new mxPoint(target.getCenterX(), source.getCenterY());
   *
   *     if (mxUtils.contains(source, pt.x, pt.y))
   *     {
   *       pt.y = source.y + source.height;
   *     }
   *
   *     result.push(pt);
   *   }
   * };
   * (end)
   *
   * In the above example, a right angle is created using a point on the
   * horizontal center of the target vertex and the vertical center of the source
   * vertex. The code checks if that point intersects the source vertex and makes
   * the edge straight if it does. The point is then added into the result array,
   * which acts as the return value of the function.
   *
   * The new edge style should then be registered in the <mxStyleRegistry> as follows:
   * (code)
   * mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);
   * (end)
   *
   * The custom edge style above can now be used in a specific edge as follows:
   *
   * (code)
   * model.setStyle(edge, 'edgeStyle=myEdgeStyle');
   * (end)
   *
   * Note that the key of the <mxStyleRegistry> entry for the function should
   * be used in string values, unless <mxGraphView.allowEval> is true, in
   * which case you can also use mxEdgeStyle.MyStyle for the value in the
   * cell style above.
   *
   * Or it can be used for all edges in the graph as follows:
   *
   * (code)
   * var style = graph.getStylesheet().getDefaultEdgeStyle();
   * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;
   * (end)
   *
   * Note that the object can be used directly when programmatically setting
   * the value, but the key in the <mxStyleRegistry> should be used when
   * setting the value via a key, value pair in a cell style.
   *
   * Function: EntityRelation
   *
   * Implements an entity relation style for edges (as used in database
   * schema diagrams). At the time the function is called, the result
   * array contains a placeholder (null) for the first absolute point,
   * that is, the point where the edge and source terminal are connected.
   * The implementation of the style then adds all intermediate waypoints
   * except for the last point, that is, the connection point between the
   * edge and the target terminal. The first ant the last point in the
   * result array are then replaced with mxPoints that take into account
   * the terminal's perimeter and next point on the edge.
   *
   * Parameters:
   *
   * state - <mxCellState> that represents the edge to be updated.
   * source - <mxCellState> that represents the source terminal.
   * target - <mxCellState> that represents the target terminal.
   * points - List of relative control points.
   * result - Array of <mxPoints> that represent the actual points of the
   * edge.
   */
  EntityRelation: function (state, source, target, points, result) {
    var view = state.view;
    var graph = view.graph;
    var segment = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT, mxConstants.ENTITY_SEGMENT) * view.scale;

    var pts = state.absolutePoints;
    var p0 = pts[0];
    var pe = pts[pts.length - 1];

    var isSourceLeft = false;

    if (source != null) {
      var sourceGeometry = graph.getCellGeometry(source.cell);

      if (sourceGeometry.relative) {
        isSourceLeft = sourceGeometry.x <= 0.5;
      } else if (target != null) {
        isSourceLeft = (pe != null ? pe.x : target.x + target.width) < (p0 != null ? p0.x : source.x);
      }
    }

    if (p0 != null) {
      source = new mxCellState();
      source.x = p0.x;
      source.y = p0.y;
    } else if (source != null) {
      var constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE);

      if (
        constraint != mxConstants.DIRECTION_MASK_NONE &&
        constraint != mxConstants.DIRECTION_MASK_WEST + mxConstants.DIRECTION_MASK_EAST
      ) {
        isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
      }
    } else {
      return;
    }

    var isTargetLeft = true;

    if (target != null) {
      var targetGeometry = graph.getCellGeometry(target.cell);

      if (targetGeometry.relative) {
        isTargetLeft = targetGeometry.x <= 0.5;
      } else if (source != null) {
        isTargetLeft = (p0 != null ? p0.x : source.x + source.width) < (pe != null ? pe.x : target.x);
      }
    }

    if (pe != null) {
      target = new mxCellState();
      target.x = pe.x;
      target.y = pe.y;
    } else if (target != null) {
      var constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE);

      if (
        constraint != mxConstants.DIRECTION_MASK_NONE &&
        constraint != mxConstants.DIRECTION_MASK_WEST + mxConstants.DIRECTION_MASK_EAST
      ) {
        isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
      }
    }

    if (source != null && target != null) {
      var x0 = isSourceLeft ? source.x : source.x + source.width;
      var y0 = view.getRoutingCenterY(source);

      var xe = isTargetLeft ? target.x : target.x + target.width;
      var ye = view.getRoutingCenterY(target);

      var seg = segment;

      var dx = isSourceLeft ? -seg : seg;
      var dep = new mxPoint(x0 + dx, y0);

      dx = isTargetLeft ? -seg : seg;
      var arr = new mxPoint(xe + dx, ye);

      // Adds intermediate points if both go out on same side
      if (isSourceLeft == isTargetLeft) {
        var x = isSourceLeft ? Math.min(x0, xe) - segment : Math.max(x0, xe) + segment;

        result.push(new mxPoint(x, y0));
        result.push(new mxPoint(x, ye));
      } else if (dep.x < arr.x == isSourceLeft) {
        var midY = y0 + (ye - y0) / 2;

        result.push(dep);
        result.push(new mxPoint(dep.x, midY));
        result.push(new mxPoint(arr.x, midY));
        result.push(arr);
      } else {
        result.push(dep);
        result.push(arr);
      }
    }
  },

  /**
   * Function: Loop
   *
   * Implements a self-reference, aka. loop.
   */
  Loop: function (state, source, target, points, result) {
    var pts = state.absolutePoints;

    var p0 = pts[0];
    var pe = pts[pts.length - 1];

    if (p0 != null && pe != null) {
      if (points != null && points.length > 0) {
        for (var i = 0; i < points.length; i++) {
          var pt = points[i];
          pt = state.view.transformControlPoint(state, pt);
          result.push(new mxPoint(pt.x, pt.y));
        }
      }

      return;
    }

    if (source != null) {
      var view = state.view;
      var graph = view.graph;
      var pt = points != null && points.length > 0 ? points[0] : null;

      if (pt != null) {
        pt = view.transformControlPoint(state, pt);

        if (mxUtils.contains(source, pt.x, pt.y)) {
          pt = null;
        }
      }

      var x = 0;
      var dx = 0;
      var y = 0;
      var dy = 0;

      var seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT, graph.gridSize) * view.scale;
      var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_WEST);

      if (dir == mxConstants.DIRECTION_NORTH || dir == mxConstants.DIRECTION_SOUTH) {
        x = view.getRoutingCenterX(source);
        dx = seg;
      } else {
        y = view.getRoutingCenterY(source);
        dy = seg;
      }

      if (pt == null || pt.x < source.x || pt.x > source.x + source.width) {
        if (pt != null) {
          x = pt.x;
          dy = Math.max(Math.abs(y - pt.y), dy);
        } else {
          if (dir == mxConstants.DIRECTION_NORTH) {
            y = source.y - 2 * dx;
          } else if (dir == mxConstants.DIRECTION_SOUTH) {
            y = source.y + source.height + 2 * dx;
          } else if (dir == mxConstants.DIRECTION_EAST) {
            x = source.x - 2 * dy;
          } else {
            x = source.x + source.width + 2 * dy;
          }
        }
      } else if (pt != null) {
        x = view.getRoutingCenterX(source);
        dx = Math.max(Math.abs(x - pt.x), dy);
        y = pt.y;
        dy = 0;
      }

      result.push(new mxPoint(x - dx, y - dy));
      result.push(new mxPoint(x + dx, y + dy));
    }
  },

  /**
   * Function: ElbowConnector
   *
   * Uses either <SideToSide> or <TopToBottom> depending on the horizontal
   * flag in the cell style. <SideToSide> is used if horizontal is true or
   * unspecified. See <EntityRelation> for a description of the
   * parameters.
   */
  ElbowConnector: function (state, source, target, points, result) {
    var pt = points != null && points.length > 0 ? points[0] : null;

    var vertical = false;
    var horizontal = false;

    if (source != null && target != null) {
      if (pt != null) {
        var left = Math.min(source.x, target.x);
        var right = Math.max(source.x + source.width, target.x + target.width);

        var top = Math.min(source.y, target.y);
        var bottom = Math.max(source.y + source.height, target.y + target.height);

        pt = state.view.transformControlPoint(state, pt);

        vertical = pt.y < top || pt.y > bottom;
        horizontal = pt.x < left || pt.x > right;
      } else {
        var left = Math.max(source.x, target.x);
        var right = Math.min(source.x + source.width, target.x + target.width);

        vertical = left == right;

        if (!vertical) {
          var top = Math.max(source.y, target.y);
          var bottom = Math.min(source.y + source.height, target.y + target.height);

          horizontal = top == bottom;
        }
      }
    }

    if (!horizontal && (vertical || state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)) {
      mxEdgeStyle.TopToBottom(state, source, target, points, result);
    } else {
      mxEdgeStyle.SideToSide(state, source, target, points, result);
    }
  },

  /**
   * Function: SideToSide
   *
   * Implements a vertical elbow edge. See <EntityRelation> for a description
   * of the parameters.
   */
  SideToSide: function (state, source, target, points, result) {
    var view = state.view;
    var pt = points != null && points.length > 0 ? points[0] : null;
    var pts = state.absolutePoints;
    var p0 = pts[0];
    var pe = pts[pts.length - 1];

    if (pt != null) {
      pt = view.transformControlPoint(state, pt);
    }

    if (p0 != null) {
      source = new mxCellState();
      source.x = p0.x;
      source.y = p0.y;
    }

    if (pe != null) {
      target = new mxCellState();
      target.x = pe.x;
      target.y = pe.y;
    }

    if (source != null && target != null) {
      var l = Math.max(source.x, target.x);
      var r = Math.min(source.x + source.width, target.x + target.width);

      var x = pt != null ? pt.x : Math.round(r + (l - r) / 2);

      var y1 = view.getRoutingCenterY(source);
      var y2 = view.getRoutingCenterY(target);

      if (pt != null) {
        if (pt.y >= source.y && pt.y <= source.y + source.height) {
          y1 = pt.y;
        }

        if (pt.y >= target.y && pt.y <= target.y + target.height) {
          y2 = pt.y;
        }
      }

      if (!mxUtils.contains(target, x, y1) && !mxUtils.contains(source, x, y1)) {
        result.push(new mxPoint(x, y1));
      }

      if (!mxUtils.contains(target, x, y2) && !mxUtils.contains(source, x, y2)) {
        result.push(new mxPoint(x, y2));
      }

      if (result.length == 1) {
        if (pt != null) {
          if (!mxUtils.contains(target, x, pt.y) && !mxUtils.contains(source, x, pt.y)) {
            result.push(new mxPoint(x, pt.y));
          }
        } else {
          var t = Math.max(source.y, target.y);
          var b = Math.min(source.y + source.height, target.y + target.height);

          result.push(new mxPoint(x, t + (b - t) / 2));
        }
      }
    }
  },

  /**
   * Function: TopToBottom
   *
   * Implements a horizontal elbow edge. See <EntityRelation> for a
   * description of the parameters.
   */
  TopToBottom: function (state, source, target, points, result) {
    var view = state.view;
    var pt = points != null && points.length > 0 ? points[0] : null;
    var pts = state.absolutePoints;
    var p0 = pts[0];
    var pe = pts[pts.length - 1];

    if (pt != null) {
      pt = view.transformControlPoint(state, pt);
    }

    if (p0 != null) {
      source = new mxCellState();
      source.x = p0.x;
      source.y = p0.y;
    }

    if (pe != null) {
      target = new mxCellState();
      target.x = pe.x;
      target.y = pe.y;
    }

    if (source != null && target != null) {
      var t = Math.max(source.y, target.y);
      var b = Math.min(source.y + source.height, target.y + target.height);

      var x = view.getRoutingCenterX(source);

      if (pt != null && pt.x >= source.x && pt.x <= source.x + source.width) {
        x = pt.x;
      }

      var y = pt != null ? pt.y : Math.round(b + (t - b) / 2);

      if (!mxUtils.contains(target, x, y) && !mxUtils.contains(source, x, y)) {
        result.push(new mxPoint(x, y));
      }

      if (pt != null && pt.x >= target.x && pt.x <= target.x + target.width) {
        x = pt.x;
      } else {
        x = view.getRoutingCenterX(target);
      }

      if (!mxUtils.contains(target, x, y) && !mxUtils.contains(source, x, y)) {
        result.push(new mxPoint(x, y));
      }

      if (result.length == 1) {
        if (pt != null && result.length == 1) {
          if (!mxUtils.contains(target, pt.x, y) && !mxUtils.contains(source, pt.x, y)) {
            result.push(new mxPoint(pt.x, y));
          }
        } else {
          var l = Math.max(source.x, target.x);
          var r = Math.min(source.x + source.width, target.x + target.width);

          result.push(new mxPoint(l + (r - l) / 2, y));
        }
      }
    }
  },

  /**
   * Function: SegmentConnector
   *
   * Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>
   * as an interactive handler for this style.
   *
   * state - <mxCellState> that represents the edge to be updated.
   * sourceScaled - <mxCellState> that represents the source terminal.
   * targetScaled - <mxCellState> that represents the target terminal.
   * controlHints - List of relative control points.
   * result - Array of <mxPoints> that represent the actual points of the
   * edge.
   *
   */
  SegmentConnector: function (state, sourceScaled, targetScaled, controlHints, result) {
    // Creates array of all way- and terminalpoints
    var pts = mxEdgeStyle.scalePointArray(state.absolutePoints, state.view.scale);
    var source = mxEdgeStyle.scaleCellState(sourceScaled, state.view.scale);
    var target = mxEdgeStyle.scaleCellState(targetScaled, state.view.scale);
    var tol = 1;

    // Whether the first segment outgoing from the source end is horizontal
    var lastPushed = result.length > 0 ? result[0] : null;
    var horizontal = true;
    var hint = null;

    // Adds waypoints only if outside of tolerance
    function pushPoint(pt) {
      pt.x = Math.round(pt.x * state.view.scale * 10) / 10;
      pt.y = Math.round(pt.y * state.view.scale * 10) / 10;

      if (
        lastPushed == null ||
        Math.abs(lastPushed.x - pt.x) >= tol ||
        Math.abs(lastPushed.y - pt.y) >= Math.max(1, state.view.scale)
      ) {
        result.push(pt);
        lastPushed = pt;
      }

      return lastPushed;
    }

    // Adds the first point
    var pt = pts[0];

    if (pt == null && source != null) {
      pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
    } else if (pt != null) {
      pt = pt.clone();
    }

    var lastInx = pts.length - 1;

    // Adds the waypoints
    if (controlHints != null && controlHints.length > 0) {
      // Converts all hints and removes nulls
      var hints = [];

      for (var i = 0; i < controlHints.length; i++) {
        var tmp = state.view.transformControlPoint(state, controlHints[i], true);

        if (tmp != null) {
          hints.push(tmp);
        }
      }

      if (hints.length == 0) {
        return;
      }

      // Aligns source and target hint to fixed points
      if (pt != null && hints[0] != null) {
        if (Math.abs(hints[0].x - pt.x) < tol) {
          hints[0].x = pt.x;
        }

        if (Math.abs(hints[0].y - pt.y) < tol) {
          hints[0].y = pt.y;
        }
      }

      var pe = pts[lastInx];

      if (pe != null && hints[hints.length - 1] != null) {
        if (Math.abs(hints[hints.length - 1].x - pe.x) < tol) {
          hints[hints.length - 1].x = pe.x;
        }

        if (Math.abs(hints[hints.length - 1].y - pe.y) < tol) {
          hints[hints.length - 1].y = pe.y;
        }
      }

      hint = hints[0];

      var currentTerm = source;
      var currentPt = pts[0];
      var hozChan = false;
      var vertChan = false;
      var currentHint = hint;

      if (currentPt != null) {
        currentTerm = null;
      }

      // Check for alignment with fixed points and with channels
      // at source and target segments only
      for (var i = 0; i < 2; i++) {
        var fixedVertAlign = currentPt != null && currentPt.x == currentHint.x;
        var fixedHozAlign = currentPt != null && currentPt.y == currentHint.y;

        var inHozChan =
          currentTerm != null && currentHint.y >= currentTerm.y && currentHint.y <= currentTerm.y + currentTerm.height;
        var inVertChan =
          currentTerm != null && currentHint.x >= currentTerm.x && currentHint.x <= currentTerm.x + currentTerm.width;

        hozChan = fixedHozAlign || (currentPt == null && inHozChan);
        vertChan = fixedVertAlign || (currentPt == null && inVertChan);

        // If the current hint falls in both the hor and vert channels in the case
        // of a floating port, or if the hint is exactly co-incident with a
        // fixed point, ignore the source and try to work out the orientation
        // from the target end
        if (i == 0 && ((hozChan && vertChan) || (fixedVertAlign && fixedHozAlign))) {
        } else {
          if (currentPt != null && !fixedHozAlign && !fixedVertAlign && (inHozChan || inVertChan)) {
            horizontal = inHozChan ? false : true;
            break;
          }

          if (vertChan || hozChan) {
            horizontal = hozChan;

            if (i == 1) {
              // Work back from target end
              horizontal = hints.length % 2 == 0 ? hozChan : vertChan;
            }

            break;
          }
        }

        currentTerm = target;
        currentPt = pts[lastInx];

        if (currentPt != null) {
          currentTerm = null;
        }

        currentHint = hints[hints.length - 1];

        if (fixedVertAlign && fixedHozAlign) {
          hints = hints.slice(1);
        }
      }

      if (
        horizontal &&
        ((pts[0] != null && pts[0].y != hint.y) ||
          (pts[0] == null && source != null && (hint.y < source.y || hint.y > source.y + source.height)))
      ) {
        pushPoint(new mxPoint(pt.x, hint.y));
      } else if (
        !horizontal &&
        ((pts[0] != null && pts[0].x != hint.x) ||
          (pts[0] == null && source != null && (hint.x < source.x || hint.x > source.x + source.width)))
      ) {
        pushPoint(new mxPoint(hint.x, pt.y));
      }

      if (horizontal) {
        pt.y = hint.y;
      } else {
        pt.x = hint.x;
      }

      for (var i = 0; i < hints.length; i++) {
        horizontal = !horizontal;
        hint = hints[i];

        //				mxLog.show();
        //				mxLog.debug('hint', i, hint.x, hint.y);

        if (horizontal) {
          pt.y = hint.y;
        } else {
          pt.x = hint.x;
        }

        pushPoint(pt.clone());
      }
    } else {
      hint = pt;
      // FIXME: First click in connect preview toggles orientation
      horizontal = true;
    }

    // Adds the last point
    pt = pts[lastInx];

    if (pt == null && target != null) {
      pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
    }

    if (pt != null) {
      if (hint != null) {
        if (
          horizontal &&
          ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||
            (pts[lastInx] == null && target != null && (hint.y < target.y || hint.y > target.y + target.height)))
        ) {
          pushPoint(new mxPoint(pt.x, hint.y));
        } else if (
          !horizontal &&
          ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||
            (pts[lastInx] == null && target != null && (hint.x < target.x || hint.x > target.x + target.width)))
        ) {
          pushPoint(new mxPoint(hint.x, pt.y));
        }
      }
    }

    // Removes bends inside the source terminal for floating ports
    if (pts[0] == null && source != null) {
      while (result.length > 1 && result[1] != null && mxUtils.contains(source, result[1].x, result[1].y)) {
        result.splice(1, 1);
      }
    }

    // Removes bends inside the target terminal
    if (pts[lastInx] == null && target != null) {
      while (
        result.length > 1 &&
        result[result.length - 1] != null &&
        mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y)
      ) {
        result.splice(result.length - 1, 1);
      }
    }

    // Removes last point if inside tolerance with end point
    if (
      pe != null &&
      result[result.length - 1] != null &&
      Math.abs(pe.x - result[result.length - 1].x) <= tol &&
      Math.abs(pe.y - result[result.length - 1].y) <= tol
    ) {
      result.splice(result.length - 1, 1);

      // Lines up second last point in result with end point
      if (result[result.length - 1] != null) {
        if (Math.abs(result[result.length - 1].x - pe.x) < tol) {
          result[result.length - 1].x = pe.x;
        }

        if (Math.abs(result[result.length - 1].y - pe.y) < tol) {
          result[result.length - 1].y = pe.y;
        }
      }
    }
  },

  orthBuffer: 10,

  orthPointsFallback: true,

  dirVectors: [
    [-1, 0],
    [0, -1],
    [1, 0],
    [0, 1],
    [-1, 0],
    [0, -1],
    [1, 0],
  ],

  wayPoints1: [
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
    [0, 0],
  ],

  routePatterns: [
    [
      [513, 2308, 2081, 2562],
      [513, 1090, 514, 2184, 2114, 2561],
      [513, 1090, 514, 2564, 2184, 2562],
      [513, 2308, 2561, 1090, 514, 2568, 2308],
    ],
    [
      [514, 1057, 513, 2308, 2081, 2562],
      [514, 2184, 2114, 2561],
      [514, 2184, 2562, 1057, 513, 2564, 2184],
      [514, 1057, 513, 2568, 2308, 2561],
    ],
    [
      [1090, 514, 1057, 513, 2308, 2081, 2562],
      [2114, 2561],
      [1090, 2562, 1057, 513, 2564, 2184],
      [1090, 514, 1057, 513, 2308, 2561, 2568],
    ],
    [
      [2081, 2562],
      [1057, 513, 1090, 514, 2184, 2114, 2561],
      [1057, 513, 1090, 514, 2184, 2562, 2564],
      [1057, 2561, 1090, 514, 2568, 2308],
    ],
  ],

  inlineRoutePatterns: [
    [null, [2114, 2568], null, null],
    [null, [514, 2081, 2114, 2568], null, null],
    [null, [2114, 2561], null, null],
    [[2081, 2562], [1057, 2114, 2568], [2184, 2562], null],
  ],
  vertexSeperations: [],

  limits: [
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0, 0, 0, 0, 0],
  ],

  LEFT_MASK: 32,

  TOP_MASK: 64,

  RIGHT_MASK: 128,

  BOTTOM_MASK: 256,

  LEFT: 1,

  TOP: 2,

  RIGHT: 4,

  BOTTOM: 8,

  // TODO remove magic numbers
  SIDE_MASK: 480,
  //mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK
  //| mxEdgeStyle.BOTTOM_MASK,

  CENTER_MASK: 512,

  SOURCE_MASK: 1024,

  TARGET_MASK: 2048,

  VERTEX_MASK: 3072,
  // mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,

  getJettySize: function (state, isSource) {
    var value = mxUtils.getValue(
      state.style,
      isSource ? mxConstants.STYLE_SOURCE_JETTY_SIZE : mxConstants.STYLE_TARGET_JETTY_SIZE,
      mxUtils.getValue(state.style, mxConstants.STYLE_JETTY_SIZE, mxEdgeStyle.orthBuffer)
    );

    if (value == 'auto') {
      // Computes the automatic jetty size
      var type = mxUtils.getValue(
        state.style,
        isSource ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW,
        mxConstants.NONE
      );

      if (type != mxConstants.NONE) {
        var size = mxUtils.getNumber(
          state.style,
          isSource ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE,
          mxConstants.DEFAULT_MARKERSIZE
        );
        value =
          Math.max(2, Math.ceil((size + mxEdgeStyle.orthBuffer) / mxEdgeStyle.orthBuffer)) * mxEdgeStyle.orthBuffer;
      } else {
        value = 2 * mxEdgeStyle.orthBuffer;
      }
    }

    return value;
  },

  /**
   * Function: scalePointArray
   *
   * Scales an array of <mxPoint>
   *
   * Parameters:
   *
   * points - array of <mxPoint> to scale
   * scale - the scaling to divide by
   *
   */
  scalePointArray: function (points, scale) {
    var result = [];

    if (points != null) {
      for (var i = 0; i < points.length; i++) {
        if (points[i] != null) {
          var pt = new mxPoint(
            Math.round((points[i].x / scale) * 10) / 10,
            Math.round((points[i].y / scale) * 10) / 10
          );
          result[i] = pt;
        } else {
          result[i] = null;
        }
      }
    } else {
      result = null;
    }

    return result;
  },

  /**
   * Function: scaleCellState
   *
   * Scales an <mxCellState>
   *
   * Parameters:
   *
   * state - <mxCellState> to scale
   * scale - the scaling to divide by
   *
   */
  scaleCellState: function (state, scale) {
    var result = null;

    if (state != null) {
      result = state.clone();
      result.setRect(
        Math.round((state.x / scale) * 10) / 10,
        Math.round((state.y / scale) * 10) / 10,
        Math.round((state.width / scale) * 10) / 10,
        Math.round((state.height / scale) * 10) / 10
      );
    } else {
      result = null;
    }

    return result;
  },

  /**
   * Function: OrthConnector
   *
   * Implements a local orthogonal router between the given
   * cells.
   *
   * Parameters:
   *
   * state - <mxCellState> that represents the edge to be updated.
   * sourceScaled - <mxCellState> that represents the source terminal.
   * targetScaled - <mxCellState> that represents the target terminal.
   * controlHints - List of relative control points.
   * result - Array of <mxPoints> that represent the actual points of the
   * edge.
   *
   */
  OrthConnector: function (state, sourceScaled, targetScaled, controlHints, result) {
    var graph = state.view.graph;
    var sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell);
    var targetEdge = target == null ? false : graph.getModel().isEdge(target.cell);

    var pts = mxEdgeStyle.scalePointArray(state.absolutePoints, state.view.scale);
    var source = mxEdgeStyle.scaleCellState(sourceScaled, state.view.scale);
    var target = mxEdgeStyle.scaleCellState(targetScaled, state.view.scale);

    var p0 = pts[0];
    var pe = pts[pts.length - 1];

    var sourceX = source != null ? source.x : p0.x;
    var sourceY = source != null ? source.y : p0.y;
    var sourceWidth = source != null ? source.width : 0;
    var sourceHeight = source != null ? source.height : 0;

    var targetX = target != null ? target.x : pe.x;
    var targetY = target != null ? target.y : pe.y;
    var targetWidth = target != null ? target.width : 0;
    var targetHeight = target != null ? target.height : 0;

    var sourceBuffer = mxEdgeStyle.getJettySize(state, true);
    var targetBuffer = mxEdgeStyle.getJettySize(state, false);

    //console.log('sourceBuffer', sourceBuffer);
    //console.log('targetBuffer', targetBuffer);
    // Workaround for loop routing within buffer zone
    if (source != null && target == source) {
      targetBuffer = Math.max(sourceBuffer, targetBuffer);
      sourceBuffer = targetBuffer;
    }

    var totalBuffer = targetBuffer + sourceBuffer;
    // console.log('totalBuffer', totalBuffer);
    var tooShort = false;

    // Checks minimum distance for fixed points and falls back to segment connector
    if (p0 != null && pe != null) {
      var dx = pe.x - p0.x;
      var dy = pe.y - p0.y;

      tooShort = dx * dx + dy * dy < totalBuffer * totalBuffer;
    }

    if (
      tooShort ||
      (mxEdgeStyle.orthPointsFallback && controlHints != null && controlHints.length > 0) ||
      sourceEdge ||
      targetEdge
    ) {
      mxEdgeStyle.SegmentConnector(state, sourceScaled, targetScaled, controlHints, result);

      return;
    }

    // Determine the side(s) of the source and target vertices
    // that the edge may connect to
    // portConstraint [source, target]
    var portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];
    var rotation = 0;

    if (source != null) {
      portConstraint[0] = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_ALL);
      rotation = mxUtils.getValue(source.style, mxConstants.STYLE_ROTATION, 0);

      //console.log('source rotation', rotation);

      if (rotation != 0) {
        var newRect = mxUtils.getBoundingBox(new mxRectangle(sourceX, sourceY, sourceWidth, sourceHeight), rotation);
        sourceX = newRect.x;
        sourceY = newRect.y;
        sourceWidth = newRect.width;
        sourceHeight = newRect.height;
      }
    }

    if (target != null) {
      portConstraint[1] = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_ALL);
      rotation = mxUtils.getValue(target.style, mxConstants.STYLE_ROTATION, 0);

      //console.log('target rotation', rotation);

      if (rotation != 0) {
        var newRect = mxUtils.getBoundingBox(new mxRectangle(targetX, targetY, targetWidth, targetHeight), rotation);
        targetX = newRect.x;
        targetY = newRect.y;
        targetWidth = newRect.width;
        targetHeight = newRect.height;
      }
    }

    //console.log('source' , sourceX, sourceY, sourceWidth, sourceHeight);
    //console.log('targetX' , targetX, targetY, targetWidth, targetHeight);

    var dir = [0, 0];

    // Work out which faces of the vertices present against each other
    // in a way that would allow a 3-segment connection if port constraints
    // permitted.
    // geo -> [source, target] [x, y, width, height]
    var geo = [
      [sourceX, sourceY, sourceWidth, sourceHeight],
      [targetX, targetY, targetWidth, targetHeight],
    ];
    var buffer = [sourceBuffer, targetBuffer];

    for (var i = 0; i < 2; i++) {
      mxEdgeStyle.limits[i][1] = geo[i][0] - buffer[i];
      mxEdgeStyle.limits[i][2] = geo[i][1] - buffer[i];
      mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + buffer[i];
      mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + buffer[i];
    }

    // Work out which quad the target is in
    var sourceCenX = geo[0][0] + geo[0][2] / 2.0;
    var sourceCenY = geo[0][1] + geo[0][3] / 2.0;
    var targetCenX = geo[1][0] + geo[1][2] / 2.0;
    var targetCenY = geo[1][1] + geo[1][3] / 2.0;

    var dx = sourceCenX - targetCenX;
    var dy = sourceCenY - targetCenY;

    var quad = 0;

    // 0 | 1
    // -----
    // 3 | 2

    if (dx < 0) {
      if (dy < 0) {
        quad = 2;
      } else {
        quad = 1;
      }
    } else {
      if (dy <= 0) {
        quad = 3;

        // Special case on x = 0 and negative y
        if (dx == 0) {
          quad = 2;
        }
      }
    }

    //console.log('quad', quad);

    // Check for connection constraints
    var currentTerm = null;

    if (source != null) {
      currentTerm = p0;
    }

    var constraint = [
      [0.5, 0.5],
      [0.5, 0.5],
    ];

    for (var i = 0; i < 2; i++) {
      if (currentTerm != null) {
        constraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2];

        if (Math.abs(currentTerm.x - geo[i][0]) <= 1) {
          dir[i] = mxConstants.DIRECTION_MASK_WEST;
        } else if (Math.abs(currentTerm.x - geo[i][0] - geo[i][2]) <= 1) {
          dir[i] = mxConstants.DIRECTION_MASK_EAST;
        }

        constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3];

        if (Math.abs(currentTerm.y - geo[i][1]) <= 1) {
          dir[i] = mxConstants.DIRECTION_MASK_NORTH;
        } else if (Math.abs(currentTerm.y - geo[i][1] - geo[i][3]) <= 1) {
          dir[i] = mxConstants.DIRECTION_MASK_SOUTH;
        }
      }

      currentTerm = null;

      if (target != null) {
        currentTerm = pe;
      }
    }

    var sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);
    var sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);
    var sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);
    var sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);

    mxEdgeStyle.vertexSeperations[1] = Math.max(sourceLeftDist - totalBuffer, 0);
    mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - totalBuffer, 0);
    mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - totalBuffer, 0);
    mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - totalBuffer, 0);

    //==============================================================
    // Start of source and target direction determination

    // Work through the preferred orientations by relative positioning
    // of the vertices and list them in preferred and available order

    var dirPref = [];
    var horPref = [];
    var vertPref = [];

    horPref[0] = sourceLeftDist >= sourceRightDist ? mxConstants.DIRECTION_MASK_WEST : mxConstants.DIRECTION_MASK_EAST;
    vertPref[0] =
      sourceTopDist >= sourceBottomDist ? mxConstants.DIRECTION_MASK_NORTH : mxConstants.DIRECTION_MASK_SOUTH;

    horPref[1] = mxUtils.reversePortConstraints(horPref[0]);
    vertPref[1] = mxUtils.reversePortConstraints(vertPref[0]);

    var preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist : sourceRightDist;
    var preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist : sourceBottomDist;

    var prefOrdering = [
      [0, 0],
      [0, 0],
    ];
    var preferredOrderSet = false;

    // If the preferred port isn't available, switch it
    for (var i = 0; i < 2; i++) {
      if (dir[i] != 0x0) {
        continue;
      }

      if ((horPref[i] & portConstraint[i]) == 0) {
        horPref[i] = mxUtils.reversePortConstraints(horPref[i]);
      }

      if ((vertPref[i] & portConstraint[i]) == 0) {
        vertPref[i] = mxUtils.reversePortConstraints(vertPref[i]);
      }

      prefOrdering[i][0] = vertPref[i];
      prefOrdering[i][1] = horPref[i];
    }

    if (preferredVertDist > 0 && preferredHorizDist > 0) {
      // Possibility of two segment edge connection
      if ((horPref[0] & portConstraint[0]) > 0 && (vertPref[1] & portConstraint[1]) > 0) {
        prefOrdering[0][0] = horPref[0];
        prefOrdering[0][1] = vertPref[0];
        prefOrdering[1][0] = vertPref[1];
        prefOrdering[1][1] = horPref[1];
        preferredOrderSet = true;
      } else if ((vertPref[0] & portConstraint[0]) > 0 && (horPref[1] & portConstraint[1]) > 0) {
        prefOrdering[0][0] = vertPref[0];
        prefOrdering[0][1] = horPref[0];
        prefOrdering[1][0] = horPref[1];
        prefOrdering[1][1] = vertPref[1];
        preferredOrderSet = true;
      }
    }

    if (preferredVertDist > 0 && !preferredOrderSet) {
      prefOrdering[0][0] = vertPref[0];
      prefOrdering[0][1] = horPref[0];
      prefOrdering[1][0] = vertPref[1];
      prefOrdering[1][1] = horPref[1];
      preferredOrderSet = true;
    }

    if (preferredHorizDist > 0 && !preferredOrderSet) {
      prefOrdering[0][0] = horPref[0];
      prefOrdering[0][1] = vertPref[0];
      prefOrdering[1][0] = horPref[1];
      prefOrdering[1][1] = vertPref[1];
      preferredOrderSet = true;
    }

    // The source and target prefs are now an ordered list of
    // the preferred port selections
    // If the list contains gaps, compact it

    for (var i = 0; i < 2; i++) {
      if (dir[i] != 0x0) {
        continue;
      }

      if ((prefOrdering[i][0] & portConstraint[i]) == 0) {
        prefOrdering[i][0] = prefOrdering[i][1];
      }

      dirPref[i] = prefOrdering[i][0] & portConstraint[i];
      dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;
      dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;
      dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;

      if ((dirPref[i] & 0xf) == 0) {
        dirPref[i] = dirPref[i] << 8;
      }

      if ((dirPref[i] & 0xf00) == 0) {
        dirPref[i] = (dirPref[i] & 0xf) | (dirPref[i] >> 8);
      }

      if ((dirPref[i] & 0xf0000) == 0) {
        dirPref[i] = (dirPref[i] & 0xffff) | ((dirPref[i] & 0xf000000) >> 8);
      }

      dir[i] = dirPref[i] & 0xf;

      if (
        portConstraint[i] == mxConstants.DIRECTION_MASK_WEST ||
        portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH ||
        portConstraint[i] == mxConstants.DIRECTION_MASK_EAST ||
        portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH
      ) {
        dir[i] = portConstraint[i];
      }
    }

    //==============================================================
    // End of source and target direction determination

    var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3 : dir[0];
    var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3 : dir[1];

    sourceIndex -= quad;
    targetIndex -= quad;

    if (sourceIndex < 1) {
      sourceIndex += 4;
    }

    if (targetIndex < 1) {
      targetIndex += 4;
    }

    var routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1];

    //console.log('routePattern', routePattern);

    mxEdgeStyle.wayPoints1[0][0] = geo[0][0];
    mxEdgeStyle.wayPoints1[0][1] = geo[0][1];

    switch (dir[0]) {
      case mxConstants.DIRECTION_MASK_WEST:
        mxEdgeStyle.wayPoints1[0][0] -= sourceBuffer;
        mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
        break;
      case mxConstants.DIRECTION_MASK_SOUTH:
        mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
        mxEdgeStyle.wayPoints1[0][1] += geo[0][3] + sourceBuffer;
        break;
      case mxConstants.DIRECTION_MASK_EAST:
        mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + sourceBuffer;
        mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
        break;
      case mxConstants.DIRECTION_MASK_NORTH:
        mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
        mxEdgeStyle.wayPoints1[0][1] -= sourceBuffer;
        break;
    }

    var currentIndex = 0;

    // Orientation, 0 horizontal, 1 vertical
    var lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0 : 1;
    var initialOrientation = lastOrientation;
    var currentOrientation = 0;

    for (var i = 0; i < routePattern.length; i++) {
      var nextDirection = routePattern[i] & 0xf;

      // Rotate the index of this direction by the quad
      // to get the real direction
      var directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3 : nextDirection;

      directionIndex += quad;

      if (directionIndex > 4) {
        directionIndex -= 4;
      }

      var direction = mxEdgeStyle.dirVectors[directionIndex - 1];

      currentOrientation = directionIndex % 2 > 0 ? 0 : 1;
      // Only update the current index if the point moved
      // in the direction of the current segment move,
      // otherwise the same point is moved until there is
      // a segment direction change
      if (currentOrientation != lastOrientation) {
        currentIndex++;
        // Copy the previous way point into the new one
        // We can't base the new position on index - 1
        // because sometime elbows turn out not to exist,
        // then we'd have to rewind.
        mxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0];
        mxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1];
      }

      var tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0;
      var sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0;
      var side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5;
      side = side << quad;

      if (side > 0xf) {
        side = side >> 4;
      }

      var center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0;

      if ((sou || tar) && side < 9) {
        var limit = 0;
        var souTar = sou ? 0 : 1;

        if (center && currentOrientation == 0) {
          limit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2];
        } else if (center) {
          limit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3];
        } else {
          limit = mxEdgeStyle.limits[souTar][side];
        }

        if (currentOrientation == 0) {
          var lastX = mxEdgeStyle.wayPoints1[currentIndex][0];
          var deltaX = (limit - lastX) * direction[0];

          if (deltaX > 0) {
            mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0] * deltaX;
          }
        } else {
          var lastY = mxEdgeStyle.wayPoints1[currentIndex][1];
          var deltaY = (limit - lastY) * direction[1];

          if (deltaY > 0) {
            mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1] * deltaY;
          }
        }
      } else if (center) {
        // Which center we're travelling to depend on the current direction
        mxEdgeStyle.wayPoints1[currentIndex][0] +=
          direction[0] * Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
        mxEdgeStyle.wayPoints1[currentIndex][1] +=
          direction[1] * Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
      }

      if (
        currentIndex > 0 &&
        mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] ==
          mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation]
      ) {
        currentIndex--;
      } else {
        lastOrientation = currentOrientation;
      }
    }

    for (var i = 0; i <= currentIndex; i++) {
      if (i == currentIndex) {
        // Last point can cause last segment to be in
        // same direction as jetty/approach. If so,
        // check the number of points is consistent
        // with the relative orientation of source and target
        // jx. Same orientation requires an even
        // number of turns (points), different requires
        // odd.
        var targetOrientation =
          (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0 : 1;
        var sameOrient = targetOrientation == initialOrientation ? 0 : 1;

        // (currentIndex + 1) % 2 is 0 for even number of points,
        // 1 for odd
        if (sameOrient != (currentIndex + 1) % 2) {
          // The last point isn't required
          break;
        }
      }

      result.push(
        new mxPoint(
          Math.round(mxEdgeStyle.wayPoints1[i][0] * state.view.scale * 10) / 10,
          Math.round(mxEdgeStyle.wayPoints1[i][1] * state.view.scale * 10) / 10
        )
      );
    }

    //console.log(result);

    // Removes duplicates
    var index = 1;

    while (index < result.length) {
      if (
        result[index - 1] == null ||
        result[index] == null ||
        result[index - 1].x != result[index].x ||
        result[index - 1].y != result[index].y
      ) {
        index++;
      } else {
        result.splice(index, 1);
      }
    }
  },

  getRoutePattern: function (dir, quad, dx, dy) {
    var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3 : dir[0];
    var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3 : dir[1];

    sourceIndex -= quad;
    targetIndex -= quad;

    if (sourceIndex < 1) {
      sourceIndex += 4;
    }
    if (targetIndex < 1) {
      targetIndex += 4;
    }

    var result = routePatterns[sourceIndex - 1][targetIndex - 1];

    if (dx == 0 || dy == 0) {
      if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null) {
        result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];
      }
    }

    return result;
  },
};

/**
 * view/mxStyleRegistry.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxStyleRegistry = {
  /**
   * Class: mxStyleRegistry
   *
   * Singleton class that acts as a global converter from string to object values
   * in a style. This is currently only used to perimeters and edge styles.
   *
   * Variable: values
   *
   * Maps from strings to objects.
   */
  values: [],

  /**
   * Function: putValue
   *
   * Puts the given object into the registry under the given name.
   */
  putValue: function (name, obj) {
    mxStyleRegistry.values[name] = obj;
  },

  /**
   * Function: getValue
   *
   * Returns the value associated with the given name.
   */
  getValue: function (name) {
    return mxStyleRegistry.values[name];
  },

  /**
   * Function: getName
   *
   * Returns the name for the given value.
   */
  getName: function (value) {
    for (var key in mxStyleRegistry.values) {
      if (mxStyleRegistry.values[key] == value) {
        return key;
      }
    }

    return null;
  },
};

mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW, mxEdgeStyle.ElbowConnector);
mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION, mxEdgeStyle.EntityRelation);
mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP, mxEdgeStyle.Loop);
mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE, mxEdgeStyle.SideToSide);
mxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM, mxEdgeStyle.TopToBottom);
mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL, mxEdgeStyle.OrthConnector);
mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT, mxEdgeStyle.SegmentConnector);

mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE, mxPerimeter.EllipsePerimeter);
mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE, mxPerimeter.RectanglePerimeter);
mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS, mxPerimeter.RhombusPerimeter);
mxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE, mxPerimeter.TrianglePerimeter);
mxStyleRegistry.putValue(mxConstants.PERIMETER_HEXAGON, mxPerimeter.HexagonPerimeter);

/**
 * view/mxGraphView.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGraphView
 *
 * Extends <mxEventSource> to implement a view for a graph. This class is in
 * charge of computing the absolute coordinates for the relative child
 * geometries, the points for perimeters and edge styles and keeping them
 * cached in <mxCellStates> for faster retrieval. The states are updated
 * whenever the model or the view state (translate, scale) changes. The scale
 * and translate are honoured in the bounds.
 *
 * Event: mxEvent.UNDO
 *
 * Fires after the root was changed in <setCurrentRoot>. The <code>edit</code>
 * property contains the <mxUndoableEdit> which contains the
 * <mxCurrentRootChange>.
 *
 * Event: mxEvent.SCALE_AND_TRANSLATE
 *
 * Fires after the scale and translate have been changed in <scaleAndTranslate>.
 * The <code>scale</code>, <code>previousScale</code>, <code>translate</code>
 * and <code>previousTranslate</code> properties contain the new and previous
 * scale and translate, respectively.
 *
 * Event: mxEvent.SCALE
 *
 * Fires after the scale was changed in <setScale>. The <code>scale</code> and
 * <code>previousScale</code> properties contain the new and previous scale.
 *
 * Event: mxEvent.TRANSLATE
 *
 * Fires after the translate was changed in <setTranslate>. The
 * <code>translate</code> and <code>previousTranslate</code> properties contain
 * the new and previous value for translate.
 *
 * Event: mxEvent.DOWN and mxEvent.UP
 *
 * Fire if the current root is changed by executing an <mxCurrentRootChange>.
 * The event name depends on the location of the root in the cell hierarchy
 * with respect to the current root. The <code>root</code> and
 * <code>previous</code> properties contain the new and previous root,
 * respectively.
 *
 * Constructor: mxGraphView
 *
 * Constructs a new view for the given <mxGraph>.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 */
function mxGraphView(graph) {
  this.graph = graph;
  this.translate = new mxPoint();
  this.graphBounds = new mxRectangle();
  this.states = new mxDictionary();
}

/**
 * Extends mxEventSource.
 */
mxGraphView.prototype = new mxEventSource();
mxGraphView.prototype.constructor = mxGraphView;

/**
 *
 */
mxGraphView.prototype.EMPTY_POINT = new mxPoint();

/**
 * Variable: doneResource
 *
 * Specifies the resource key for the status message after a long operation.
 * If the resource for this key does not exist then the value is used as
 * the status message. Default is 'done'.
 */
mxGraphView.prototype.doneResource = mxClient.language != 'none' ? 'done' : '';

/**
 * Function: updatingDocumentResource
 *
 * Specifies the resource key for the status message while the document is
 * being updated. If the resource for this key does not exist then the
 * value is used as the status message. Default is 'updatingDocument'.
 */
mxGraphView.prototype.updatingDocumentResource = mxClient.language != 'none' ? 'updatingDocument' : '';

/**
 * Variable: allowEval
 *
 * Specifies if string values in cell styles should be evaluated using
 * <mxUtils.eval>. This will only be used if the string values can't be mapped
 * to objects using <mxStyleRegistry>. Default is false. NOTE: Enabling this
 * switch carries a possible security risk.
 */
mxGraphView.prototype.allowEval = false;

/**
 * Variable: captureDocumentGesture
 *
 * Specifies if a gesture should be captured when it goes outside of the
 * graph container. Default is true.
 */
mxGraphView.prototype.captureDocumentGesture = true;

/**
 * Variable: optimizeVmlReflows
 *
 * Specifies if the <canvas> should be hidden while rendering in IE8 standards
 * mode and quirks mode. This will significantly improve rendering performance.
 * Default is true.
 */
mxGraphView.prototype.optimizeVmlReflows = true;

/**
 * Variable: rendering
 *
 * Specifies if shapes should be created, updated and destroyed using the
 * methods of <mxCellRenderer> in <graph>. Default is true.
 */
mxGraphView.prototype.rendering = true;

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxGraphView.prototype.graph = null;

/**
 * Variable: currentRoot
 *
 * <mxCell> that acts as the root of the displayed cell hierarchy.
 */
mxGraphView.prototype.currentRoot = null;

/**
 * Variable: graphBounds
 *
 * <mxRectangle> that caches the scales, translated bounds of the current view.
 */
mxGraphView.prototype.graphBounds = null;

/**
 * Variable: scale
 *
 * Specifies the scale. Default is 1 (100%).
 */
mxGraphView.prototype.scale = 1;

/**
 * Variable: translate
 *
 * <mxPoint> that specifies the current translation. Default is a new
 * empty <mxPoint>.
 */
mxGraphView.prototype.translate = null;

/**
 * Variable: states
 *
 * <mxDictionary> that maps from cell IDs to <mxCellStates>.
 */
mxGraphView.prototype.states = null;

/**
 * Variable: updateStyle
 *
 * Specifies if the style should be updated in each validation step. If this
 * is false then the style is only updated if the state is created or if the
 * style of the cell was changed. Default is false.
 */
mxGraphView.prototype.updateStyle = false;

/**
 * Variable: lastNode
 *
 * During validation, this contains the last DOM node that was processed.
 */
mxGraphView.prototype.lastNode = null;

/**
 * Variable: lastHtmlNode
 *
 * During validation, this contains the last HTML DOM node that was processed.
 */
mxGraphView.prototype.lastHtmlNode = null;

/**
 * Variable: lastForegroundNode
 *
 * During validation, this contains the last edge's DOM node that was processed.
 */
mxGraphView.prototype.lastForegroundNode = null;

/**
 * Variable: lastForegroundHtmlNode
 *
 * During validation, this contains the last edge HTML DOM node that was processed.
 */
mxGraphView.prototype.lastForegroundHtmlNode = null;

/**
 * Function: getGraphBounds
 *
 * Returns <graphBounds>.
 */
mxGraphView.prototype.getGraphBounds = function () {
  return this.graphBounds;
};

/**
 * Function: setGraphBounds
 *
 * Sets <graphBounds>.
 */
mxGraphView.prototype.setGraphBounds = function (value) {
  this.graphBounds = value;
};

/**
 * Function: getBounds
 *
 * Returns the union of all <mxCellStates> for the given array of <mxCells>.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose bounds should be returned.
 */
mxGraphView.prototype.getBounds = function (cells) {
  var result = null;

  if (cells != null && cells.length > 0) {
    var model = this.graph.getModel();

    for (var i = 0; i < cells.length; i++) {
      if (model.isVertex(cells[i]) || model.isEdge(cells[i])) {
        var state = this.getState(cells[i]);

        if (state != null) {
          if (result == null) {
            result = mxRectangle.fromRectangle(state);
          } else {
            result.add(state);
          }
        }
      }
    }
  }

  return result;
};

/**
 * Function: setCurrentRoot
 *
 * Sets and returns the current root and fires an <undo> event before
 * calling <mxGraph.sizeDidChange>.
 *
 * Parameters:
 *
 * root - <mxCell> that specifies the root of the displayed cell hierarchy.
 */
mxGraphView.prototype.setCurrentRoot = function (root) {
  if (this.currentRoot != root) {
    var change = new mxCurrentRootChange(this, root);
    change.execute();
    var edit = new mxUndoableEdit(this, true);
    edit.add(change);
    this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
    this.graph.sizeDidChange();
  }

  return root;
};

/**
 * Function: scaleAndTranslate
 *
 * Sets the scale and translation and fires a <scale> and <translate> event
 * before calling <revalidate> followed by <mxGraph.sizeDidChange>.
 *
 * Parameters:
 *
 * scale - Decimal value that specifies the new scale (1 is 100%).
 * dx - X-coordinate of the translation.
 * dy - Y-coordinate of the translation.
 */
mxGraphView.prototype.scaleAndTranslate = function (scale, dx, dy) {
  var previousScale = this.scale;
  var previousTranslate = new mxPoint(this.translate.x, this.translate.y);

  if (this.scale != scale || this.translate.x != dx || this.translate.y != dy) {
    this.scale = scale;

    this.translate.x = dx;
    this.translate.y = dy;

    if (this.isEventsEnabled()) {
      this.viewStateChanged();
    }
  }

  this.fireEvent(
    new mxEventObject(
      mxEvent.SCALE_AND_TRANSLATE,
      'scale',
      scale,
      'previousScale',
      previousScale,
      'translate',
      this.translate,
      'previousTranslate',
      previousTranslate
    )
  );
};

/**
 * Function: getScale
 *
 * Returns the <scale>.
 */
mxGraphView.prototype.getScale = function () {
  return this.scale;
};

/**
 * Function: setScale
 *
 * Sets the scale and fires a <scale> event before calling <revalidate> followed
 * by <mxGraph.sizeDidChange>.
 *
 * Parameters:
 *
 * value - Decimal value that specifies the new scale (1 is 100%).
 */
mxGraphView.prototype.setScale = function (value) {
  var previousScale = this.scale;

  if (this.scale != value) {
    this.scale = value;

    if (this.isEventsEnabled()) {
      this.viewStateChanged();
    }
  }

  this.fireEvent(new mxEventObject(mxEvent.SCALE, 'scale', value, 'previousScale', previousScale));
};

/**
 * Function: getTranslate
 *
 * Returns the <translate>.
 */
mxGraphView.prototype.getTranslate = function () {
  return this.translate;
};

/**
 * Function: setTranslate
 *
 * Sets the translation and fires a <translate> event before calling
 * <revalidate> followed by <mxGraph.sizeDidChange>. The translation is the
 * negative of the origin.
 *
 * Parameters:
 *
 * dx - X-coordinate of the translation.
 * dy - Y-coordinate of the translation.
 */
mxGraphView.prototype.setTranslate = function (dx, dy) {
  var previousTranslate = new mxPoint(this.translate.x, this.translate.y);

  if (this.translate.x != dx || this.translate.y != dy) {
    this.translate.x = dx;
    this.translate.y = dy;

    if (this.isEventsEnabled()) {
      this.viewStateChanged();
    }
  }

  this.fireEvent(
    new mxEventObject(mxEvent.TRANSLATE, 'translate', this.translate, 'previousTranslate', previousTranslate)
  );
};

/**
 * Function: viewStateChanged
 *
 * Invoked after <scale> and/or <translate> has changed.
 */
mxGraphView.prototype.viewStateChanged = function () {
  this.revalidate();
  this.graph.sizeDidChange();
};

/**
 * Function: refresh
 *
 * Clears the view if <currentRoot> is not null and revalidates.
 */
mxGraphView.prototype.refresh = function () {
  if (this.currentRoot != null) {
    this.clear();
  }

  this.revalidate();
};

/**
 * Function: revalidate
 *
 * Revalidates the complete view with all cell states.
 */
mxGraphView.prototype.revalidate = function () {
  this.invalidate();
  this.validate();
};

/**
 * Function: clear
 *
 * Removes the state of the given cell and all descendants if the given
 * cell is not the current root.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> for which the state should be removed. Default
 * is the root of the model.
 * force - Boolean indicating if the current root should be ignored for
 * recursion.
 */
mxGraphView.prototype.clear = function (cell, force, recurse) {
  var model = this.graph.getModel();
  cell = cell || model.getRoot();
  force = force != null ? force : false;
  recurse = recurse != null ? recurse : true;

  this.removeState(cell);

  if (recurse && (force || cell != this.currentRoot)) {
    var childCount = model.getChildCount(cell);

    for (var i = 0; i < childCount; i++) {
      this.clear(model.getChildAt(cell, i), force);
    }
  } else {
    this.invalidate(cell);
  }
};

/**
 * Function: invalidate
 *
 * Invalidates the state of the given cell, all its descendants and
 * connected edges.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> to be invalidated. Default is the root of the
 * model.
 */
mxGraphView.prototype.invalidate = function (cell, recurse, includeEdges) {
  var model = this.graph.getModel();
  cell = cell || model.getRoot();
  recurse = recurse != null ? recurse : true;
  includeEdges = includeEdges != null ? includeEdges : true;

  var state = this.getState(cell);

  if (state != null) {
    state.invalid = true;
  }

  // Avoids infinite loops for invalid graphs
  if (!cell.invalidating) {
    cell.invalidating = true;

    // Recursively invalidates all descendants
    if (recurse) {
      var childCount = model.getChildCount(cell);

      for (var i = 0; i < childCount; i++) {
        var child = model.getChildAt(cell, i);
        this.invalidate(child, recurse, includeEdges);
      }
    }

    // Propagates invalidation to all connected edges
    if (includeEdges) {
      var edgeCount = model.getEdgeCount(cell);

      for (var i = 0; i < edgeCount; i++) {
        this.invalidate(model.getEdgeAt(cell, i), recurse, includeEdges);
      }
    }

    delete cell.invalidating;
  }
};

/**
 * Function: validate
 *
 * Calls <validateCell> and <validateCellState> and updates the <graphBounds>
 * using <getBoundingBox>. Finally the background is validated using
 * <validateBackground>.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> to be used as the root of the validation.
 * Default is <currentRoot> or the root of the model.
 */
mxGraphView.prototype.validate = function (cell) {
  var t0 = mxLog.enter('mxGraphView.validate');
  window.status = mxResources.get(this.updatingDocumentResource) || this.updatingDocumentResource;

  this.resetValidationState();

  // Improves IE rendering speed by minimizing reflows
  var prevDisplay = null;

  if (
    this.optimizeVmlReflows &&
    this.canvas != null &&
    this.textDiv == null &&
    ((document.documentMode == 8 && !mxClient.IS_EM) || mxClient.IS_QUIRKS)
  ) {
    // Placeholder keeps scrollbar positions when canvas is hidden
    this.placeholder = document.createElement('div');
    this.placeholder.style.position = 'absolute';
    this.placeholder.style.width = this.canvas.clientWidth + 'px';
    this.placeholder.style.height = this.canvas.clientHeight + 'px';
    this.canvas.parentNode.appendChild(this.placeholder);

    prevDisplay = this.drawPane.style.display;
    this.canvas.style.display = 'none';

    // Creates temporary DIV used for text measuring in mxText.updateBoundingBox
    this.textDiv = document.createElement('div');
    this.textDiv.style.position = 'absolute';
    this.textDiv.style.whiteSpace = 'nowrap';
    this.textDiv.style.visibility = 'hidden';
    this.textDiv.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block';
    this.textDiv.style.zoom = '1';

    document.body.appendChild(this.textDiv);
  }

  var graphBounds = this.getBoundingBox(
    this.validateCellState(
      this.validateCell(cell || (this.currentRoot != null ? this.currentRoot : this.graph.getModel().getRoot()))
    )
  );
  this.setGraphBounds(graphBounds != null ? graphBounds : this.getEmptyBounds());
  this.validateBackground();

  if (prevDisplay != null) {
    this.canvas.style.display = prevDisplay;
    this.textDiv.parentNode.removeChild(this.textDiv);

    if (this.placeholder != null) {
      this.placeholder.parentNode.removeChild(this.placeholder);
    }

    // Textdiv cannot be reused
    this.textDiv = null;
  }

  this.resetValidationState();

  window.status = mxResources.get(this.doneResource) || this.doneResource;
  mxLog.leave('mxGraphView.validate', t0);
};

/**
 * Function: getEmptyBounds
 *
 * Returns the bounds for an empty graph. This returns a rectangle at
 * <translate> with the size of 0 x 0.
 */
mxGraphView.prototype.getEmptyBounds = function () {
  return new mxRectangle(this.translate.x * this.scale, this.translate.y * this.scale);
};

/**
 * Function: getBoundingBox
 *
 * Returns the bounding box of the shape and the label for the given
 * <mxCellState> and its children if recurse is true.
 *
 * Parameters:
 *
 * state - <mxCellState> whose bounding box should be returned.
 * recurse - Optional boolean indicating if the children should be included.
 * Default is true.
 */
mxGraphView.prototype.getBoundingBox = function (state, recurse) {
  recurse = recurse != null ? recurse : true;
  var bbox = null;

  if (state != null) {
    if (state.shape != null && state.shape.boundingBox != null) {
      bbox = state.shape.boundingBox.clone();
    }

    // Adds label bounding box to graph bounds
    if (state.text != null && state.text.boundingBox != null) {
      if (bbox != null) {
        bbox.add(state.text.boundingBox);
      } else {
        bbox = state.text.boundingBox.clone();
      }
    }

    if (recurse) {
      var model = this.graph.getModel();
      var childCount = model.getChildCount(state.cell);

      for (var i = 0; i < childCount; i++) {
        var bounds = this.getBoundingBox(this.getState(model.getChildAt(state.cell, i)));

        if (bounds != null) {
          if (bbox == null) {
            bbox = bounds;
          } else {
            bbox.add(bounds);
          }
        }
      }
    }
  }

  return bbox;
};

/**
 * Function: createBackgroundPageShape
 *
 * Creates and returns the shape used as the background page.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that represents the bounds of the shape.
 */
mxGraphView.prototype.createBackgroundPageShape = function (bounds) {
  return new mxRectangleShape(bounds, 'white', 'black');
};

/**
 * Function: validateBackground
 *
 * Calls <validateBackgroundImage> and <validateBackgroundPage>.
 */
mxGraphView.prototype.validateBackground = function () {
  this.validateBackgroundImage();
  this.validateBackgroundPage();
};

/**
 * Function: validateBackgroundImage
 *
 * Validates the background image.
 */
mxGraphView.prototype.validateBackgroundImage = function () {
  var bg = this.graph.getBackgroundImage();

  if (bg != null) {
    if (this.backgroundImage == null || this.backgroundImage.image != bg.src) {
      if (this.backgroundImage != null) {
        this.backgroundImage.destroy();
      }

      var bounds = new mxRectangle(0, 0, 1, 1);

      this.backgroundImage = new mxImageShape(bounds, bg.src);
      this.backgroundImage.dialect = this.graph.dialect;
      this.backgroundImage.init(this.backgroundPane);
      this.backgroundImage.redraw();

      // Workaround for ignored event on background in IE8 standards mode
      if (document.documentMode == 8 && !mxClient.IS_EM) {
        mxEvent.addGestureListeners(
          this.backgroundImage.node,
          mxUtils.bind(this, function (evt) {
            this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
          }),
          mxUtils.bind(this, function (evt) {
            this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
          }),
          mxUtils.bind(this, function (evt) {
            this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
          })
        );
      }
    }

    this.redrawBackgroundImage(this.backgroundImage, bg);
  } else if (this.backgroundImage != null) {
    this.backgroundImage.destroy();
    this.backgroundImage = null;
  }
};

/**
 * Function: validateBackgroundPage
 *
 * Validates the background page.
 */
mxGraphView.prototype.validateBackgroundPage = function () {
  if (this.graph.pageVisible) {
    var bounds = this.getBackgroundPageBounds();

    if (this.backgroundPageShape == null) {
      this.backgroundPageShape = this.createBackgroundPageShape(bounds);
      this.backgroundPageShape.scale = this.scale;
      this.backgroundPageShape.isShadow = true;
      this.backgroundPageShape.dialect = this.graph.dialect;
      this.backgroundPageShape.init(this.backgroundPane);
      this.backgroundPageShape.redraw();

      // Adds listener for double click handling on background
      if (this.graph.nativeDblClickEnabled) {
        mxEvent.addListener(
          this.backgroundPageShape.node,
          'dblclick',
          mxUtils.bind(this, function (evt) {
            this.graph.dblClick(evt);
          })
        );
      }

      // Adds basic listeners for graph event dispatching outside of the
      // container and finishing the handling of a single gesture
      mxEvent.addGestureListeners(
        this.backgroundPageShape.node,
        mxUtils.bind(this, function (evt) {
          this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
        }),
        mxUtils.bind(this, function (evt) {
          // Hides the tooltip if mouse is outside container
          if (this.graph.tooltipHandler != null && this.graph.tooltipHandler.isHideOnHover()) {
            this.graph.tooltipHandler.hide();
          }

          if (this.graph.isMouseDown && !mxEvent.isConsumed(evt)) {
            this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
          }
        }),
        mxUtils.bind(this, function (evt) {
          this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
        })
      );
    } else {
      this.backgroundPageShape.scale = this.scale;
      this.backgroundPageShape.bounds = bounds;
      this.backgroundPageShape.redraw();
    }
  } else if (this.backgroundPageShape != null) {
    this.backgroundPageShape.destroy();
    this.backgroundPageShape = null;
  }
};

/**
 * Function: getBackgroundPageBounds
 *
 * Returns the bounds for the background page.
 */
mxGraphView.prototype.getBackgroundPageBounds = function () {
  var fmt = this.graph.pageFormat;
  var ps = this.scale * this.graph.pageScale;
  var bounds = new mxRectangle(
    this.scale * this.translate.x,
    this.scale * this.translate.y,
    fmt.width * ps,
    fmt.height * ps
  );

  return bounds;
};

/**
 * Function: redrawBackgroundImage
 *
 * Updates the bounds and redraws the background image.
 *
 * Example:
 *
 * If the background image should not be scaled, this can be replaced with
 * the following.
 *
 * (code)
 * mxGraphView.prototype.redrawBackground = function(backgroundImage, bg)
 * {
 *   backgroundImage.bounds.x = this.translate.x;
 *   backgroundImage.bounds.y = this.translate.y;
 *   backgroundImage.bounds.width = bg.width;
 *   backgroundImage.bounds.height = bg.height;
 *
 *   backgroundImage.redraw();
 * };
 * (end)
 *
 * Parameters:
 *
 * backgroundImage - <mxImageShape> that represents the background image.
 * bg - <mxImage> that specifies the image and its dimensions.
 */
mxGraphView.prototype.redrawBackgroundImage = function (backgroundImage, bg) {
  backgroundImage.scale = this.scale;
  backgroundImage.bounds.x = this.scale * this.translate.x;
  backgroundImage.bounds.y = this.scale * this.translate.y;
  backgroundImage.bounds.width = this.scale * bg.width;
  backgroundImage.bounds.height = this.scale * bg.height;

  backgroundImage.redraw();
};

/**
 * Function: validateCell
 *
 * Recursively creates the cell state for the given cell if visible is true and
 * the given cell is visible. If the cell is not visible but the state exists
 * then it is removed using <removeState>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose <mxCellState> should be created.
 * visible - Optional boolean indicating if the cell should be visible. Default
 * is true.
 */
mxGraphView.prototype.validateCell = function (cell, visible) {
  visible = visible != null ? visible : true;

  if (cell != null) {
    visible = visible && this.graph.isCellVisible(cell);
    var state = this.getState(cell, visible);

    if (state != null && !visible) {
      this.removeState(cell);
    } else {
      var model = this.graph.getModel();
      var childCount = model.getChildCount(cell);

      for (var i = 0; i < childCount; i++) {
        this.validateCell(
          model.getChildAt(cell, i),
          visible && (!this.isCellCollapsed(cell) || cell == this.currentRoot)
        );
      }
    }
  }

  return cell;
};

/**
 * Function: validateCellState
 *
 * Validates and repaints the <mxCellState> for the given <mxCell>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose <mxCellState> should be validated.
 * recurse - Optional boolean indicating if the children of the cell should be
 * validated. Default is true.
 */
mxGraphView.prototype.validateCellState = function (cell, recurse) {
  recurse = recurse != null ? recurse : true;
  var state = null;

  if (cell != null) {
    state = this.getState(cell);

    if (state != null) {
      var model = this.graph.getModel();

      if (state.invalid) {
        state.invalid = false;

        if (state.style == null || state.invalidStyle) {
          state.style = this.graph.getCellStyle(state.cell);
          state.invalidStyle = false;
        }

        if (cell != this.currentRoot) {
          this.validateCellState(model.getParent(cell), false);
        }

        state.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, true), false), true);
        state.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, false), false), false);

        this.updateCellState(state);

        // Repaint happens immediately after the cell is validated
        if (cell != this.currentRoot && !state.invalid) {
          this.graph.cellRenderer.redraw(state, false, this.isRendering());

          // Handles changes to invertex paintbounds after update of rendering shape
          state.updateCachedBounds();
        }
      }

      if (recurse && !state.invalid) {
        // Updates order in DOM if recursively traversing
        if (state.shape != null) {
          this.stateValidated(state);
        }

        var childCount = model.getChildCount(cell);

        for (var i = 0; i < childCount; i++) {
          this.validateCellState(model.getChildAt(cell, i));
        }
      }
    }
  }

  return state;
};

/**
 * Function: updateCellState
 *
 * Updates the given <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> to be updated.
 */
mxGraphView.prototype.updateCellState = function (state) {
  state.absoluteOffset.x = 0;
  state.absoluteOffset.y = 0;
  state.origin.x = 0;
  state.origin.y = 0;
  state.length = 0;

  if (state.cell != this.currentRoot) {
    var model = this.graph.getModel();
    var pState = this.getState(model.getParent(state.cell));

    if (pState != null && pState.cell != this.currentRoot) {
      state.origin.x += pState.origin.x;
      state.origin.y += pState.origin.y;
    }

    var offset = this.graph.getChildOffsetForCell(state.cell);

    if (offset != null) {
      state.origin.x += offset.x;
      state.origin.y += offset.y;
    }

    var geo = this.graph.getCellGeometry(state.cell);

    if (geo != null) {
      if (!model.isEdge(state.cell)) {
        offset = geo.offset != null ? geo.offset : this.EMPTY_POINT;

        if (geo.relative && pState != null) {
          if (model.isEdge(pState.cell)) {
            var origin = this.getPoint(pState, geo);

            if (origin != null) {
              state.origin.x += origin.x / this.scale - pState.origin.x - this.translate.x;
              state.origin.y += origin.y / this.scale - pState.origin.y - this.translate.y;
            }
          } else {
            state.origin.x += geo.x * pState.unscaledWidth + offset.x;
            state.origin.y += geo.y * pState.unscaledHeight + offset.y;
          }
        } else {
          state.absoluteOffset.x = this.scale * offset.x;
          state.absoluteOffset.y = this.scale * offset.y;
          state.origin.x += geo.x;
          state.origin.y += geo.y;
        }
      }

      state.x = this.scale * (this.translate.x + state.origin.x);
      state.y = this.scale * (this.translate.y + state.origin.y);
      state.width = this.scale * geo.width;
      state.unscaledWidth = geo.width;
      state.height = this.scale * geo.height;
      state.unscaledHeight = geo.height;

      if (model.isVertex(state.cell)) {
        this.updateVertexState(state, geo);
      }

      if (model.isEdge(state.cell)) {
        this.updateEdgeState(state, geo);
      }
    }
  }

  state.updateCachedBounds();
};

/**
 * Function: isCellCollapsed
 *
 * Returns true if the children of the given cell should not be visible in the
 * view. This implementation uses <mxGraph.isCellVisible> but it can be
 * overidden to use a separate condition.
 */
mxGraphView.prototype.isCellCollapsed = function (cell) {
  return this.graph.isCellCollapsed(cell);
};

/**
 * Function: updateVertexState
 *
 * Validates the given cell state.
 */
mxGraphView.prototype.updateVertexState = function (state, geo) {
  var model = this.graph.getModel();
  var pState = this.getState(model.getParent(state.cell));

  if (geo.relative && pState != null && !model.isEdge(pState.cell)) {
    var alpha = mxUtils.toRadians(pState.style[mxConstants.STYLE_ROTATION] || '0');

    if (alpha != 0) {
      var cos = Math.cos(alpha);
      var sin = Math.sin(alpha);

      var ct = new mxPoint(state.getCenterX(), state.getCenterY());
      var cx = new mxPoint(pState.getCenterX(), pState.getCenterY());
      var pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);
      state.x = pt.x - state.width / 2;
      state.y = pt.y - state.height / 2;
    }
  }

  this.updateVertexLabelOffset(state);
};

/**
 * Function: updateEdgeState
 *
 * Validates the given cell state.
 */
mxGraphView.prototype.updateEdgeState = function (state, geo) {
  var source = state.getVisibleTerminalState(true);
  var target = state.getVisibleTerminalState(false);

  // This will remove edges with no terminals and no terminal points
  // as such edges are invalid and produce NPEs in the edge styles.
  // Also removes connected edges that have no visible terminals.
  if (
    (this.graph.model.getTerminal(state.cell, true) != null && source == null) ||
    (source == null && geo.getTerminalPoint(true) == null) ||
    (this.graph.model.getTerminal(state.cell, false) != null && target == null) ||
    (target == null && geo.getTerminalPoint(false) == null)
  ) {
    this.clear(state.cell, true);
  } else {
    this.updateFixedTerminalPoints(state, source, target);
    this.updatePoints(state, geo.points, source, target);
    this.updateFloatingTerminalPoints(state, source, target);

    var pts = state.absolutePoints;

    if (
      state.cell != this.currentRoot &&
      (pts == null || pts.length < 2 || pts[0] == null || pts[pts.length - 1] == null)
    ) {
      // This will remove edges with invalid points from the list of states in the view.
      // Happens if the one of the terminals and the corresponding terminal point is null.
      this.clear(state.cell, true);
    } else {
      this.updateEdgeBounds(state);
      this.updateEdgeLabelOffset(state);
    }
  }
};

/**
 * Function: updateVertexLabelOffset
 *
 * Updates the absoluteOffset of the given vertex cell state. This takes
 * into account the label position styles.
 *
 * Parameters:
 *
 * state - <mxCellState> whose absolute offset should be updated.
 */
mxGraphView.prototype.updateVertexLabelOffset = function (state) {
  var h = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);

  if (h == mxConstants.ALIGN_LEFT) {
    var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);

    if (lw != null) {
      lw *= this.scale;
    } else {
      lw = state.width;
    }

    state.absoluteOffset.x -= lw;
  } else if (h == mxConstants.ALIGN_RIGHT) {
    state.absoluteOffset.x += state.width;
  } else if (h == mxConstants.ALIGN_CENTER) {
    var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);

    if (lw != null) {
      // Aligns text block with given width inside the vertex width
      var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
      var dx = 0;

      if (align == mxConstants.ALIGN_CENTER) {
        dx = 0.5;
      } else if (align == mxConstants.ALIGN_RIGHT) {
        dx = 1;
      }

      if (dx != 0) {
        state.absoluteOffset.x -= (lw * this.scale - state.width) * dx;
      }
    }
  }

  var v = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);

  if (v == mxConstants.ALIGN_TOP) {
    state.absoluteOffset.y -= state.height;
  } else if (v == mxConstants.ALIGN_BOTTOM) {
    state.absoluteOffset.y += state.height;
  }
};

/**
 * Function: resetValidationState
 *
 * Resets the current validation state.
 */
mxGraphView.prototype.resetValidationState = function () {
  this.lastNode = null;
  this.lastHtmlNode = null;
  this.lastForegroundNode = null;
  this.lastForegroundHtmlNode = null;
};

/**
 * Function: stateValidated
 *
 * Invoked when a state has been processed in <validatePoints>. This is used
 * to update the order of the DOM nodes of the shape.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the cell state.
 */
mxGraphView.prototype.stateValidated = function (state) {
  var fg =
    (this.graph.getModel().isEdge(state.cell) && this.graph.keepEdgesInForeground) ||
    (this.graph.getModel().isVertex(state.cell) && this.graph.keepEdgesInBackground);
  var htmlNode = fg ? this.lastForegroundHtmlNode || this.lastHtmlNode : this.lastHtmlNode;
  var node = fg ? this.lastForegroundNode || this.lastNode : this.lastNode;
  var result = this.graph.cellRenderer.insertStateAfter(state, node, htmlNode);

  if (fg) {
    this.lastForegroundHtmlNode = result[1];
    this.lastForegroundNode = result[0];
  } else {
    this.lastHtmlNode = result[1];
    this.lastNode = result[0];
  }
};

/**
 * Function: updateFixedTerminalPoints
 *
 * Sets the initial absolute terminal points in the given state before the edge
 * style is computed.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose initial terminal points should be updated.
 * source - <mxCellState> which represents the source terminal.
 * target - <mxCellState> which represents the target terminal.
 */
mxGraphView.prototype.updateFixedTerminalPoints = function (edge, source, target) {
  this.updateFixedTerminalPoint(edge, source, true, this.graph.getConnectionConstraint(edge, source, true));
  this.updateFixedTerminalPoint(edge, target, false, this.graph.getConnectionConstraint(edge, target, false));
};

/**
 * Function: updateFixedTerminalPoint
 *
 * Sets the fixed source or target terminal point on the given edge.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose terminal point should be updated.
 * terminal - <mxCellState> which represents the actual terminal.
 * source - Boolean that specifies if the terminal is the source.
 * constraint - <mxConnectionConstraint> that specifies the connection.
 */
mxGraphView.prototype.updateFixedTerminalPoint = function (edge, terminal, source, constraint) {
  edge.setAbsoluteTerminalPoint(this.getFixedTerminalPoint(edge, terminal, source, constraint), source);
};

/**
 * Function: getFixedTerminalPoint
 *
 * Returns the fixed source or target terminal point for the given edge.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose terminal point should be returned.
 * terminal - <mxCellState> which represents the actual terminal.
 * source - Boolean that specifies if the terminal is the source.
 * constraint - <mxConnectionConstraint> that specifies the connection.
 */
mxGraphView.prototype.getFixedTerminalPoint = function (edge, terminal, source, constraint) {
  var pt = null;

  if (constraint != null) {
    pt = this.graph.getConnectionPoint(terminal, constraint, false); // FIXME Rounding introduced bugs when calculating label positions -> , this.graph.isOrthogonal(edge));
  }

  if (pt == null && terminal == null) {
    var s = this.scale;
    var tr = this.translate;
    var orig = edge.origin;
    var geo = this.graph.getCellGeometry(edge.cell);
    pt = geo.getTerminalPoint(source);

    if (pt != null) {
      pt = new mxPoint(s * (tr.x + pt.x + orig.x), s * (tr.y + pt.y + orig.y));
    }
  }

  return pt;
};

/**
 * Function: updateBoundsFromStencil
 *
 * Updates the bounds of the given cell state to reflect the bounds of the stencil
 * if it has a fixed aspect and returns the previous bounds as an <mxRectangle> if
 * the bounds have been modified or null otherwise.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose bounds should be updated.
 */
mxGraphView.prototype.updateBoundsFromStencil = function (state) {
  var previous = null;

  if (state != null && state.shape != null && state.shape.stencil != null && state.shape.stencil.aspect == 'fixed') {
    previous = mxRectangle.fromRectangle(state);
    var asp = state.shape.stencil.computeAspect(state.style, state.x, state.y, state.width, state.height);
    state.setRect(asp.x, asp.y, state.shape.stencil.w0 * asp.width, state.shape.stencil.h0 * asp.height);
  }

  return previous;
};

/**
 * Function: updatePoints
 *
 * Updates the absolute points in the given state using the specified array
 * of <mxPoints> as the relative points.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose absolute points should be updated.
 * points - Array of <mxPoints> that constitute the relative points.
 * source - <mxCellState> that represents the source terminal.
 * target - <mxCellState> that represents the target terminal.
 */
mxGraphView.prototype.updatePoints = function (edge, points, source, target) {
  if (edge != null) {
    var pts = [];
    pts.push(edge.absolutePoints[0]);
    var edgeStyle = this.getEdgeStyle(edge, points, source, target);

    if (edgeStyle != null) {
      var src = this.getTerminalPort(edge, source, true);
      var trg = this.getTerminalPort(edge, target, false);

      // Uses the stencil bounds for routing and restores after routing
      var srcBounds = this.updateBoundsFromStencil(src);
      var trgBounds = this.updateBoundsFromStencil(trg);

      edgeStyle(edge, src, trg, points, pts);

      // Restores previous bounds
      if (srcBounds != null) {
        src.setRect(srcBounds.x, srcBounds.y, srcBounds.width, srcBounds.height);
      }

      if (trgBounds != null) {
        trg.setRect(trgBounds.x, trgBounds.y, trgBounds.width, trgBounds.height);
      }
    } else if (points != null) {
      for (var i = 0; i < points.length; i++) {
        if (points[i] != null) {
          var pt = mxUtils.clone(points[i]);
          pts.push(this.transformControlPoint(edge, pt));
        }
      }
    }

    var tmp = edge.absolutePoints;
    pts.push(tmp[tmp.length - 1]);

    edge.absolutePoints = pts;
  }
};

/**
 * Function: transformControlPoint
 *
 * Transforms the given control point to an absolute point.
 */
mxGraphView.prototype.transformControlPoint = function (state, pt, ignoreScale) {
  if (state != null && pt != null) {
    var orig = state.origin;
    var scale = ignoreScale ? 1 : this.scale;

    return new mxPoint(scale * (pt.x + this.translate.x + orig.x), scale * (pt.y + this.translate.y + orig.y));
  }

  return null;
};

/**
 * Function: isLoopStyleEnabled
 *
 * Returns true if the given edge should be routed with <mxGraph.defaultLoopStyle>
 * or the <mxConstants.STYLE_LOOP> defined for the given edge. This implementation
 * returns true if the given edge is a loop and does not have connections constraints
 * associated.
 */
mxGraphView.prototype.isLoopStyleEnabled = function (edge, points, source, target) {
  var sc = this.graph.getConnectionConstraint(edge, source, true);
  var tc = this.graph.getConnectionConstraint(edge, target, false);

  if (
    (points == null || points.length < 2) &&
    (!mxUtils.getValue(edge.style, mxConstants.STYLE_ORTHOGONAL_LOOP, false) ||
      ((sc == null || sc.point == null) && (tc == null || tc.point == null)))
  ) {
    return source != null && source == target;
  }

  return false;
};

/**
 * Function: getEdgeStyle
 *
 * Returns the edge style function to be used to render the given edge state.
 */
mxGraphView.prototype.getEdgeStyle = function (edge, points, source, target) {
  var edgeStyle = this.isLoopStyleEnabled(edge, points, source, target)
    ? mxUtils.getValue(edge.style, mxConstants.STYLE_LOOP, this.graph.defaultLoopStyle)
    : !mxUtils.getValue(edge.style, mxConstants.STYLE_NOEDGESTYLE, false)
    ? edge.style[mxConstants.STYLE_EDGE]
    : null;

  // Converts string values to objects
  if (typeof edgeStyle == 'string') {
    var tmp = mxStyleRegistry.getValue(edgeStyle);

    if (tmp == null && this.isAllowEval()) {
      tmp = mxUtils.eval(edgeStyle);
    }

    edgeStyle = tmp;
  }

  if (typeof edgeStyle == 'function') {
    return edgeStyle;
  }

  return null;
};

/**
 * Function: updateFloatingTerminalPoints
 *
 * Updates the terminal points in the given state after the edge style was
 * computed for the edge.
 *
 * Parameters:
 *
 * state - <mxCellState> whose terminal points should be updated.
 * source - <mxCellState> that represents the source terminal.
 * target - <mxCellState> that represents the target terminal.
 */
mxGraphView.prototype.updateFloatingTerminalPoints = function (state, source, target) {
  var pts = state.absolutePoints;
  var p0 = pts[0];
  var pe = pts[pts.length - 1];

  if (pe == null && target != null) {
    this.updateFloatingTerminalPoint(state, target, source, false);
  }

  if (p0 == null && source != null) {
    this.updateFloatingTerminalPoint(state, source, target, true);
  }
};

/**
 * Function: updateFloatingTerminalPoint
 *
 * Updates the absolute terminal point in the given state for the given
 * start and end state, where start is the source if source is true.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose terminal point should be updated.
 * start - <mxCellState> for the terminal on "this" side of the edge.
 * end - <mxCellState> for the terminal on the other side of the edge.
 * source - Boolean indicating if start is the source terminal state.
 */
mxGraphView.prototype.updateFloatingTerminalPoint = function (edge, start, end, source) {
  edge.setAbsoluteTerminalPoint(this.getFloatingTerminalPoint(edge, start, end, source), source);
};

/**
 * Function: getFloatingTerminalPoint
 *
 * Returns the floating terminal point for the given edge, start and end
 * state, where start is the source if source is true.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose terminal point should be returned.
 * start - <mxCellState> for the terminal on "this" side of the edge.
 * end - <mxCellState> for the terminal on the other side of the edge.
 * source - Boolean indicating if start is the source terminal state.
 */
mxGraphView.prototype.getFloatingTerminalPoint = function (edge, start, end, source) {
  start = this.getTerminalPort(edge, start, source);
  var next = this.getNextPoint(edge, end, source);

  var orth = this.graph.isOrthogonal(edge);
  var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));
  var center = new mxPoint(start.getCenterX(), start.getCenterY());

  if (alpha != 0) {
    var cos = Math.cos(-alpha);
    var sin = Math.sin(-alpha);
    next = mxUtils.getRotatedPoint(next, cos, sin, center);
  }

  var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
  border += parseFloat(
    edge.style[source ? mxConstants.STYLE_SOURCE_PERIMETER_SPACING : mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0
  );
  var pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border);

  if (alpha != 0) {
    var cos = Math.cos(alpha);
    var sin = Math.sin(alpha);
    pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
  }

  return pt;
};

/**
 * Function: getTerminalPort
 *
 * Returns an <mxCellState> that represents the source or target terminal or
 * port for the given edge.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the state of the edge.
 * terminal - <mxCellState> that represents the terminal.
 * source - Boolean indicating if the given terminal is the source terminal.
 */
mxGraphView.prototype.getTerminalPort = function (state, terminal, source) {
  var key = source ? mxConstants.STYLE_SOURCE_PORT : mxConstants.STYLE_TARGET_PORT;
  var id = mxUtils.getValue(state.style, key);

  if (id != null) {
    var tmp = this.getState(this.graph.getModel().getCell(id));

    // Only uses ports where a cell state exists
    if (tmp != null) {
      terminal = tmp;
    }
  }

  return terminal;
};

/**
 * Function: getPerimeterPoint
 *
 * Returns an <mxPoint> that defines the location of the intersection point between
 * the perimeter and the line between the center of the shape and the given point.
 *
 * Parameters:
 *
 * terminal - <mxCellState> for the source or target terminal.
 * next - <mxPoint> that lies outside of the given terminal.
 * orthogonal - Boolean that specifies if the orthogonal projection onto
 * the perimeter should be returned. If this is false then the intersection
 * of the perimeter and the line between the next and the center point is
 * returned.
 * border - Optional border between the perimeter and the shape.
 */
mxGraphView.prototype.getPerimeterPoint = function (terminal, next, orthogonal, border) {
  var point = null;

  if (terminal != null) {
    var perimeter = this.getPerimeterFunction(terminal);

    if (perimeter != null && next != null) {
      var bounds = this.getPerimeterBounds(terminal, border);

      if (bounds.width > 0 || bounds.height > 0) {
        point = new mxPoint(next.x, next.y);
        var flipH = false;
        var flipV = false;

        if (this.graph.model.isVertex(terminal.cell)) {
          flipH = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPH, 0) == 1;
          flipV = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPV, 0) == 1;

          // Legacy support for stencilFlipH/V
          if (terminal.shape != null && terminal.shape.stencil != null) {
            flipH = mxUtils.getValue(terminal.style, 'stencilFlipH', 0) == 1 || flipH;
            flipV = mxUtils.getValue(terminal.style, 'stencilFlipV', 0) == 1 || flipV;
          }

          if (flipH) {
            point.x = 2 * bounds.getCenterX() - point.x;
          }

          if (flipV) {
            point.y = 2 * bounds.getCenterY() - point.y;
          }
        }

        point = perimeter(bounds, terminal, point, orthogonal);

        if (point != null) {
          if (flipH) {
            point.x = 2 * bounds.getCenterX() - point.x;
          }

          if (flipV) {
            point.y = 2 * bounds.getCenterY() - point.y;
          }
        }
      }
    }

    if (point == null) {
      point = this.getPoint(terminal);
    }
  }

  return point;
};

/**
 * Function: getRoutingCenterX
 *
 * Returns the x-coordinate of the center point for automatic routing.
 */
mxGraphView.prototype.getRoutingCenterX = function (state) {
  var f = state.style != null ? parseFloat(state.style[mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;

  return state.getCenterX() + f * state.width;
};

/**
 * Function: getRoutingCenterY
 *
 * Returns the y-coordinate of the center point for automatic routing.
 */
mxGraphView.prototype.getRoutingCenterY = function (state) {
  var f = state.style != null ? parseFloat(state.style[mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;

  return state.getCenterY() + f * state.height;
};

/**
 * Function: getPerimeterBounds
 *
 * Returns the perimeter bounds for the given terminal, edge pair as an
 * <mxRectangle>.
 *
 * If you have a model where each terminal has a relative child that should
 * act as the graphical endpoint for a connection from/to the terminal, then
 * this method can be replaced as follows:
 *
 * (code)
 * var oldGetPerimeterBounds = mxGraphView.prototype.getPerimeterBounds;
 * mxGraphView.prototype.getPerimeterBounds = function(terminal, edge, isSource)
 * {
 *   var model = this.graph.getModel();
 *   var childCount = model.getChildCount(terminal.cell);
 *
 *   if (childCount > 0)
 *   {
 *     var child = model.getChildAt(terminal.cell, 0);
 *     var geo = model.getGeometry(child);
 *
 *     if (geo != null &&
 *         geo.relative)
 *     {
 *       var state = this.getState(child);
 *
 *       if (state != null)
 *       {
 *         terminal = state;
 *       }
 *     }
 *   }
 *
 *   return oldGetPerimeterBounds.apply(this, arguments);
 * };
 * (end)
 *
 * Parameters:
 *
 * terminal - <mxCellState> that represents the terminal.
 * border - Number that adds a border between the shape and the perimeter.
 */
mxGraphView.prototype.getPerimeterBounds = function (terminal, border) {
  border = border != null ? border : 0;

  if (terminal != null) {
    border += parseFloat(terminal.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
  }

  return terminal.getPerimeterBounds(border * this.scale);
};

/**
 * Function: getPerimeterFunction
 *
 * Returns the perimeter function for the given state.
 */
mxGraphView.prototype.getPerimeterFunction = function (state) {
  var perimeter = state.style[mxConstants.STYLE_PERIMETER];

  // Converts string values to objects
  if (typeof perimeter == 'string') {
    var tmp = mxStyleRegistry.getValue(perimeter);

    if (tmp == null && this.isAllowEval()) {
      tmp = mxUtils.eval(perimeter);
    }

    perimeter = tmp;
  }

  if (typeof perimeter == 'function') {
    return perimeter;
  }

  return null;
};

/**
 * Function: getNextPoint
 *
 * Returns the nearest point in the list of absolute points or the center
 * of the opposite terminal.
 *
 * Parameters:
 *
 * edge - <mxCellState> that represents the edge.
 * opposite - <mxCellState> that represents the opposite terminal.
 * source - Boolean indicating if the next point for the source or target
 * should be returned.
 */
mxGraphView.prototype.getNextPoint = function (edge, opposite, source) {
  var pts = edge.absolutePoints;
  var point = null;

  if (pts != null && pts.length >= 2) {
    var count = pts.length;
    point = pts[source ? Math.min(1, count - 1) : Math.max(0, count - 2)];
  }

  if (point == null && opposite != null) {
    point = new mxPoint(opposite.getCenterX(), opposite.getCenterY());
  }

  return point;
};

/**
 * Function: getVisibleTerminal
 *
 * Returns the nearest ancestor terminal that is visible. The edge appears
 * to be connected to this terminal on the display. The result of this method
 * is cached in <mxCellState.getVisibleTerminalState>.
 *
 * Parameters:
 *
 * edge - <mxCell> whose visible terminal should be returned.
 * source - Boolean that specifies if the source or target terminal
 * should be returned.
 */
mxGraphView.prototype.getVisibleTerminal = function (edge, source) {
  var model = this.graph.getModel();
  var result = model.getTerminal(edge, source);
  var best = result;

  while (result != null && result != this.currentRoot) {
    if (!this.graph.isCellVisible(best) || this.isCellCollapsed(result)) {
      best = result;
    }

    result = model.getParent(result);
  }

  // Checks if the result is valid for the current view state
  if (best != null && (!model.contains(best) || model.getParent(best) == model.getRoot() || best == this.currentRoot)) {
    best = null;
  }

  return best;
};

/**
 * Function: updateEdgeBounds
 *
 * Updates the given state using the bounding box of t
 * he absolute points.
 * Also updates <mxCellState.terminalDistance>, <mxCellState.length> and
 * <mxCellState.segments>.
 *
 * Parameters:
 *
 * state - <mxCellState> whose bounds should be updated.
 */
mxGraphView.prototype.updateEdgeBounds = function (state) {
  var points = state.absolutePoints;
  var p0 = points[0];
  var pe = points[points.length - 1];

  if (p0.x != pe.x || p0.y != pe.y) {
    var dx = pe.x - p0.x;
    var dy = pe.y - p0.y;
    state.terminalDistance = Math.sqrt(dx * dx + dy * dy);
  } else {
    state.terminalDistance = 0;
  }

  var length = 0;
  var segments = [];
  var pt = p0;

  if (pt != null) {
    var minX = pt.x;
    var minY = pt.y;
    var maxX = minX;
    var maxY = minY;

    for (var i = 1; i < points.length; i++) {
      var tmp = points[i];

      if (tmp != null) {
        var dx = pt.x - tmp.x;
        var dy = pt.y - tmp.y;

        var segment = Math.sqrt(dx * dx + dy * dy);
        segments.push(segment);
        length += segment;

        pt = tmp;

        minX = Math.min(pt.x, minX);
        minY = Math.min(pt.y, minY);
        maxX = Math.max(pt.x, maxX);
        maxY = Math.max(pt.y, maxY);
      }
    }

    state.length = length;
    state.segments = segments;

    var markerSize = 1; // TODO: include marker size

    state.x = minX;
    state.y = minY;
    state.width = Math.max(markerSize, maxX - minX);
    state.height = Math.max(markerSize, maxY - minY);
  }
};

/**
 * Function: getPoint
 *
 * Returns the absolute point on the edge for the given relative
 * <mxGeometry> as an <mxPoint>. The edge is represented by the given
 * <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the state of the parent edge.
 * geometry - <mxGeometry> that represents the relative location.
 */
mxGraphView.prototype.getPoint = function (state, geometry) {
  var x = state.getCenterX();
  var y = state.getCenterY();

  if (state.segments != null && (geometry == null || geometry.relative)) {
    var gx = geometry != null ? geometry.x / 2 : 0;
    var pointCount = state.absolutePoints.length;
    var dist = Math.round((gx + 0.5) * state.length);
    var segment = state.segments[0];
    var length = 0;
    var index = 1;

    while (dist >= Math.round(length + segment) && index < pointCount - 1) {
      length += segment;
      segment = state.segments[index++];
    }

    var factor = segment == 0 ? 0 : (dist - length) / segment;
    var p0 = state.absolutePoints[index - 1];
    var pe = state.absolutePoints[index];

    if (p0 != null && pe != null) {
      var gy = 0;
      var offsetX = 0;
      var offsetY = 0;

      if (geometry != null) {
        gy = geometry.y;
        var offset = geometry.offset;

        if (offset != null) {
          offsetX = offset.x;
          offsetY = offset.y;
        }
      }

      var dx = pe.x - p0.x;
      var dy = pe.y - p0.y;
      var nx = segment == 0 ? 0 : dy / segment;
      var ny = segment == 0 ? 0 : dx / segment;

      x = p0.x + dx * factor + (nx * gy + offsetX) * this.scale;
      y = p0.y + dy * factor - (ny * gy - offsetY) * this.scale;
    }
  } else if (geometry != null) {
    var offset = geometry.offset;

    if (offset != null) {
      x += offset.x;
      y += offset.y;
    }
  }

  return new mxPoint(x, y);
};

/**
 * Function: getRelativePoint
 *
 * Gets the relative point that describes the given, absolute label
 * position for the given edge state.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the state of the parent edge.
 * x - Specifies the x-coordinate of the absolute label location.
 * y - Specifies the y-coordinate of the absolute label location.
 */
mxGraphView.prototype.getRelativePoint = function (edgeState, x, y) {
  var model = this.graph.getModel();
  var geometry = model.getGeometry(edgeState.cell);

  if (geometry != null) {
    var pointCount = edgeState.absolutePoints.length;

    if (geometry.relative && pointCount > 1) {
      var totalLength = edgeState.length;
      var segments = edgeState.segments;

      // Works out which line segment the point of the label is closest to
      var p0 = edgeState.absolutePoints[0];
      var pe = edgeState.absolutePoints[1];
      var minDist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
      var length = 0;
      var index = 0;
      var tmp = 0;

      for (var i = 2; i < pointCount; i++) {
        p0 = pe;
        pe = edgeState.absolutePoints[i];
        var dist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
        tmp += segments[i - 2];

        if (dist <= minDist) {
          minDist = dist;
          index = i - 1;
          length = tmp;
        }
      }

      var seg = segments[index];
      p0 = edgeState.absolutePoints[index];
      pe = edgeState.absolutePoints[index + 1];

      var x2 = p0.x;
      var y2 = p0.y;

      var x1 = pe.x;
      var y1 = pe.y;

      var px = x;
      var py = y;

      var xSegment = x2 - x1;
      var ySegment = y2 - y1;

      px -= x1;
      py -= y1;
      var projlenSq = 0;

      px = xSegment - px;
      py = ySegment - py;
      var dotprod = px * xSegment + py * ySegment;

      if (dotprod <= 0.0) {
        projlenSq = 0;
      } else {
        projlenSq = (dotprod * dotprod) / (xSegment * xSegment + ySegment * ySegment);
      }

      var projlen = Math.sqrt(projlenSq);

      if (projlen > seg) {
        projlen = seg;
      }

      var yDistance = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y));
      var direction = mxUtils.relativeCcw(p0.x, p0.y, pe.x, pe.y, x, y);

      if (direction == -1) {
        yDistance = -yDistance;
      }

      // Constructs the relative point for the label
      return new mxPoint(((totalLength / 2 - length - projlen) / totalLength) * -2, yDistance / this.scale);
    }
  }

  return new mxPoint();
};

/**
 * Function: updateEdgeLabelOffset
 *
 * Updates <mxCellState.absoluteOffset> for the given state. The absolute
 * offset is normally used for the position of the edge label. Is is
 * calculated from the geometry as an absolute offset from the center
 * between the two endpoints if the geometry is absolute, or as the
 * relative distance between the center along the line and the absolute
 * orthogonal distance if the geometry is relative.
 *
 * Parameters:
 *
 * state - <mxCellState> whose absolute offset should be updated.
 */
mxGraphView.prototype.updateEdgeLabelOffset = function (state) {
  var points = state.absolutePoints;

  state.absoluteOffset.x = state.getCenterX();
  state.absoluteOffset.y = state.getCenterY();

  if (points != null && points.length > 0 && state.segments != null) {
    var geometry = this.graph.getCellGeometry(state.cell);

    if (geometry.relative) {
      var offset = this.getPoint(state, geometry);

      if (offset != null) {
        state.absoluteOffset = offset;
      }
    } else {
      var p0 = points[0];
      var pe = points[points.length - 1];

      if (p0 != null && pe != null) {
        var dx = pe.x - p0.x;
        var dy = pe.y - p0.y;
        var x0 = 0;
        var y0 = 0;

        var off = geometry.offset;

        if (off != null) {
          x0 = off.x;
          y0 = off.y;
        }

        var x = p0.x + dx / 2 + x0 * this.scale;
        var y = p0.y + dy / 2 + y0 * this.scale;

        state.absoluteOffset.x = x;
        state.absoluteOffset.y = y;
      }
    }
  }
};

/**
 * Function: getState
 *
 * Returns the <mxCellState> for the given cell. If create is true, then
 * the state is created if it does not yet exist.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the <mxCellState> should be returned.
 * create - Optional boolean indicating if a new state should be created
 * if it does not yet exist. Default is false.
 */
mxGraphView.prototype.getState = function (cell, create) {
  create = create || false;
  var state = null;

  if (cell != null) {
    state = this.states.get(cell);

    if (create && (state == null || this.updateStyle) && this.graph.isCellVisible(cell)) {
      if (state == null) {
        state = this.createState(cell);
        this.states.put(cell, state);
      } else {
        state.style = this.graph.getCellStyle(cell);
      }
    }
  }

  return state;
};

/**
 * Function: isRendering
 *
 * Returns <rendering>.
 */
mxGraphView.prototype.isRendering = function () {
  return this.rendering;
};

/**
 * Function: setRendering
 *
 * Sets <rendering>.
 */
mxGraphView.prototype.setRendering = function (value) {
  this.rendering = value;
};

/**
 * Function: isAllowEval
 *
 * Returns <allowEval>.
 */
mxGraphView.prototype.isAllowEval = function () {
  return this.allowEval;
};

/**
 * Function: setAllowEval
 *
 * Sets <allowEval>.
 */
mxGraphView.prototype.setAllowEval = function (value) {
  this.allowEval = value;
};

/**
 * Function: getStates
 *
 * Returns <states>.
 */
mxGraphView.prototype.getStates = function () {
  return this.states;
};

/**
 * Function: setStates
 *
 * Sets <states>.
 */
mxGraphView.prototype.setStates = function (value) {
  this.states = value;
};

/**
 * Function: getCellStates
 *
 * Returns the <mxCellStates> for the given array of <mxCells>. The array
 * contains all states that are not null, that is, the returned array may
 * have less elements than the given array. If no argument is given, then
 * this returns <states>.
 */
mxGraphView.prototype.getCellStates = function (cells) {
  if (cells == null) {
    return this.states;
  } else {
    var result = [];

    for (var i = 0; i < cells.length; i++) {
      var state = this.getState(cells[i]);

      if (state != null) {
        result.push(state);
      }
    }

    return result;
  }
};

/**
 * Function: removeState
 *
 * Removes and returns the <mxCellState> for the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the <mxCellState> should be removed.
 */
mxGraphView.prototype.removeState = function (cell) {
  var state = null;

  if (cell != null) {
    state = this.states.remove(cell);

    if (state != null) {
      this.graph.cellRenderer.destroy(state);
      state.invalid = true;
      state.destroy();
    }
  }

  return state;
};

/**
 * Function: createState
 *
 * Creates and returns an <mxCellState> for the given cell and initializes
 * it using <mxCellRenderer.initialize>.
 *
 * Parameters:
 *
 * cell - <mxCell> for which a new <mxCellState> should be created.
 */
mxGraphView.prototype.createState = function (cell) {
  return new mxCellState(this, cell, this.graph.getCellStyle(cell));
};

/**
 * Function: getCanvas
 *
 * Returns the DOM node that contains the background-, draw- and
 * overlay- and decoratorpanes.
 */
mxGraphView.prototype.getCanvas = function () {
  return this.canvas;
};

/**
 * Function: getBackgroundPane
 *
 * Returns the DOM node that represents the background layer.
 */
mxGraphView.prototype.getBackgroundPane = function () {
  return this.backgroundPane;
};

/**
 * Function: getDrawPane
 *
 * Returns the DOM node that represents the main drawing layer.
 */
mxGraphView.prototype.getDrawPane = function () {
  return this.drawPane;
};

/**
 * Function: getOverlayPane
 *
 * Returns the DOM node that represents the layer above the drawing layer.
 */
mxGraphView.prototype.getOverlayPane = function () {
  return this.overlayPane;
};

/**
 * Function: getDecoratorPane
 *
 * Returns the DOM node that represents the topmost drawing layer.
 */
mxGraphView.prototype.getDecoratorPane = function () {
  return this.decoratorPane;
};

/**
 * Function: isContainerEvent
 *
 * Returns true if the event origin is one of the drawing panes or
 * containers of the view.
 */
mxGraphView.prototype.isContainerEvent = function (evt) {
  var source = mxEvent.getSource(evt);

  return (
    source == this.graph.container ||
    source.parentNode == this.backgroundPane ||
    (source.parentNode != null && source.parentNode.parentNode == this.backgroundPane) ||
    source == this.canvas.parentNode ||
    source == this.canvas ||
    source == this.backgroundPane ||
    source == this.drawPane ||
    source == this.overlayPane ||
    source == this.decoratorPane
  );
};

/**
 * Function: isScrollEvent
 *
 * Returns true if the event origin is one of the scrollbars of the
 * container in IE. Such events are ignored.
 */
mxGraphView.prototype.isScrollEvent = function (evt) {
  var offset = mxUtils.getOffset(this.graph.container);
  var pt = new mxPoint(evt.clientX - offset.x, evt.clientY - offset.y);

  var outWidth = this.graph.container.offsetWidth;
  var inWidth = this.graph.container.clientWidth;

  if (outWidth > inWidth && pt.x > inWidth + 2 && pt.x <= outWidth) {
    return true;
  }

  var outHeight = this.graph.container.offsetHeight;
  var inHeight = this.graph.container.clientHeight;

  if (outHeight > inHeight && pt.y > inHeight + 2 && pt.y <= outHeight) {
    return true;
  }

  return false;
};

/**
 * Function: init
 *
 * Initializes the graph event dispatch loop for the specified container
 * and invokes <create> to create the required DOM nodes for the display.
 */
mxGraphView.prototype.init = function () {
  this.installListeners();

  // Creates the DOM nodes for the respective display dialect
  var graph = this.graph;

  if (graph.dialect == mxConstants.DIALECT_SVG) {
    this.createSvg();
  } else if (graph.dialect == mxConstants.DIALECT_VML) {
    this.createVml();
  } else {
    this.createHtml();
  }
};

/**
 * Function: installListeners
 *
 * Installs the required listeners in the container.
 */
mxGraphView.prototype.installListeners = function () {
  var graph = this.graph;
  var container = graph.container;

  if (container != null) {
    // Support for touch device gestures (eg. pinch to zoom)
    // Double-tap handling is implemented in mxGraph.fireMouseEvent
    if (mxClient.IS_TOUCH) {
      mxEvent.addListener(
        container,
        'gesturestart',
        mxUtils.bind(this, function (evt) {
          graph.fireGestureEvent(evt);
          mxEvent.consume(evt);
        })
      );

      mxEvent.addListener(
        container,
        'gesturechange',
        mxUtils.bind(this, function (evt) {
          graph.fireGestureEvent(evt);
          mxEvent.consume(evt);
        })
      );

      mxEvent.addListener(
        container,
        'gestureend',
        mxUtils.bind(this, function (evt) {
          graph.fireGestureEvent(evt);
          mxEvent.consume(evt);
        })
      );
    }

    // Fires event only for one pointer per gesture
    var pointerId = null;

    // Adds basic listeners for graph event dispatching
    mxEvent.addGestureListeners(
      container,
      mxUtils.bind(this, function (evt) {
        // Condition to avoid scrollbar events starting a rubberband selection
        if (
          this.isContainerEvent(evt) &&
          ((!mxClient.IS_IE && !mxClient.IS_IE11 && !mxClient.IS_GC && !mxClient.IS_OP && !mxClient.IS_SF) ||
            !this.isScrollEvent(evt))
        ) {
          graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
          pointerId = evt.pointerId;
        }
      }),
      mxUtils.bind(this, function (evt) {
        if (this.isContainerEvent(evt) && (pointerId == null || evt.pointerId == pointerId)) {
          graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
        }
      }),
      mxUtils.bind(this, function (evt) {
        if (this.isContainerEvent(evt)) {
          graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
        }

        pointerId = null;
      })
    );

    // Adds listener for double click handling on background, this does always
    // use native event handler, we assume that the DOM of the background
    // does not change during the double click
    mxEvent.addListener(
      container,
      'dblclick',
      mxUtils.bind(this, function (evt) {
        if (this.isContainerEvent(evt)) {
          graph.dblClick(evt);
        }
      })
    );

    // Workaround for touch events which started on some DOM node
    // on top of the container, in which case the cells under the
    // mouse for the move and up events are not detected.
    var getState = function (evt) {
      var state = null;

      // Workaround for touch events which started on some DOM node
      // on top of the container, in which case the cells under the
      // mouse for the move and up events are not detected.
      if (mxClient.IS_TOUCH) {
        var x = mxEvent.getClientX(evt);
        var y = mxEvent.getClientY(evt);

        // Dispatches the drop event to the graph which
        // consumes and executes the source function
        var pt = mxUtils.convertPoint(container, x, y);
        state = graph.view.getState(graph.getCellAt(pt.x, pt.y));
      }

      return state;
    };

    // Adds basic listeners for graph event dispatching outside of the
    // container and finishing the handling of a single gesture
    // Implemented via graph event dispatch loop to avoid duplicate events
    // in Firefox and Chrome
    graph.addMouseListener({
      mouseDown: function (sender, me) {
        graph.popupMenuHandler.hideMenu();
      },
      mouseMove: function () {},
      mouseUp: function () {},
    });

    this.moveHandler = mxUtils.bind(this, function (evt) {
      // Hides the tooltip if mouse is outside container
      if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) {
        graph.tooltipHandler.hide();
      }

      if (
        this.captureDocumentGesture &&
        graph.isMouseDown &&
        graph.container != null &&
        !this.isContainerEvent(evt) &&
        graph.container.style.display != 'none' &&
        graph.container.style.visibility != 'hidden' &&
        !mxEvent.isConsumed(evt)
      ) {
        graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
      }
    });

    this.endHandler = mxUtils.bind(this, function (evt) {
      if (
        this.captureDocumentGesture &&
        graph.isMouseDown &&
        graph.container != null &&
        !this.isContainerEvent(evt) &&
        graph.container.style.display != 'none' &&
        graph.container.style.visibility != 'hidden'
      ) {
        graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
      }
    });

    mxEvent.addGestureListeners(document, null, this.moveHandler, this.endHandler);
  }
};

/**
 * Function: createHtml
 *
 * Creates the DOM nodes for the HTML display.
 */
mxGraphView.prototype.createHtml = function () {
  var container = this.graph.container;

  if (container != null) {
    this.canvas = this.createHtmlPane('100%', '100%');
    this.canvas.style.overflow = 'hidden';

    // Uses minimal size for inner DIVs on Canvas. This is required
    // for correct event processing in IE. If we have an overlapping
    // DIV then the events on the cells are only fired for labels.
    this.backgroundPane = this.createHtmlPane('1px', '1px');
    this.drawPane = this.createHtmlPane('1px', '1px');
    this.overlayPane = this.createHtmlPane('1px', '1px');
    this.decoratorPane = this.createHtmlPane('1px', '1px');

    this.canvas.appendChild(this.backgroundPane);
    this.canvas.appendChild(this.drawPane);
    this.canvas.appendChild(this.overlayPane);
    this.canvas.appendChild(this.decoratorPane);

    container.appendChild(this.canvas);
    this.updateContainerStyle(container);

    // Implements minWidth/minHeight in quirks mode
    if (mxClient.IS_QUIRKS) {
      var onResize = mxUtils.bind(this, function (evt) {
        var bounds = this.getGraphBounds();
        var width = bounds.x + bounds.width + this.graph.border;
        var height = bounds.y + bounds.height + this.graph.border;

        this.updateHtmlCanvasSize(width, height);
      });

      mxEvent.addListener(window, 'resize', onResize);
    }
  }
};

/**
 * Function: updateHtmlCanvasSize
 *
 * Updates the size of the HTML canvas.
 */
mxGraphView.prototype.updateHtmlCanvasSize = function (width, height) {
  if (this.graph.container != null) {
    var ow = this.graph.container.offsetWidth;
    var oh = this.graph.container.offsetHeight;

    if (ow < width) {
      this.canvas.style.width = width + 'px';
    } else {
      this.canvas.style.width = '100%';
    }

    if (oh < height) {
      this.canvas.style.height = height + 'px';
    } else {
      this.canvas.style.height = '100%';
    }
  }
};

/**
 * Function: createHtmlPane
 *
 * Creates and returns a drawing pane in HTML (DIV).
 */
mxGraphView.prototype.createHtmlPane = function (width, height) {
  var pane = document.createElement('DIV');

  if (width != null && height != null) {
    pane.style.position = 'absolute';
    pane.style.left = '0px';
    pane.style.top = '0px';

    pane.style.width = width;
    pane.style.height = height;
  } else {
    pane.style.position = 'relative';
  }

  return pane;
};

/**
 * Function: createVml
 *
 * Creates the DOM nodes for the VML display.
 */
mxGraphView.prototype.createVml = function () {
  var container = this.graph.container;

  if (container != null) {
    var width = container.offsetWidth;
    var height = container.offsetHeight;
    this.canvas = this.createVmlPane(width, height);
    this.canvas.style.overflow = 'hidden';

    this.backgroundPane = this.createVmlPane(width, height);
    this.drawPane = this.createVmlPane(width, height);
    this.overlayPane = this.createVmlPane(width, height);
    this.decoratorPane = this.createVmlPane(width, height);

    this.canvas.appendChild(this.backgroundPane);
    this.canvas.appendChild(this.drawPane);
    this.canvas.appendChild(this.overlayPane);
    this.canvas.appendChild(this.decoratorPane);

    container.appendChild(this.canvas);
  }
};

/**
 * Function: createVmlPane
 *
 * Creates a drawing pane in VML (group).
 */
mxGraphView.prototype.createVmlPane = function (width, height) {
  var pane = document.createElement(mxClient.VML_PREFIX + ':group');

  // At this point the width and height are potentially
  // uninitialized. That's OK.
  pane.style.position = 'absolute';
  pane.style.left = '0px';
  pane.style.top = '0px';

  pane.style.width = width + 'px';
  pane.style.height = height + 'px';

  pane.setAttribute('coordsize', width + ',' + height);
  pane.setAttribute('coordorigin', '0,0');

  return pane;
};

/**
 * Function: createSvg
 *
 * Creates and returns the DOM nodes for the SVG display.
 */
mxGraphView.prototype.createSvg = function () {
  var container = this.graph.container;
  this.canvas = document.createElementNS(mxConstants.NS_SVG, 'g');

  // For background image
  this.backgroundPane = document.createElementNS(mxConstants.NS_SVG, 'g');
  this.canvas.appendChild(this.backgroundPane);

  // Adds two layers (background is early feature)
  this.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
  this.canvas.appendChild(this.drawPane);

  this.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
  this.canvas.appendChild(this.overlayPane);

  this.decoratorPane = document.createElementNS(mxConstants.NS_SVG, 'g');
  this.canvas.appendChild(this.decoratorPane);

  var root = document.createElementNS(mxConstants.NS_SVG, 'svg');
  root.style.left = '0px';
  root.style.top = '0px';
  root.style.width = '100%';
  root.style.height = '100%';

  // NOTE: In standards mode, the SVG must have block layout
  // in order for the container DIV to not show scrollbars.
  root.style.display = 'block';
  root.appendChild(this.canvas);

  // Workaround for scrollbars in IE11 and below
  if (mxClient.IS_IE || mxClient.IS_IE11) {
    root.style.overflow = 'hidden';
  }

  if (container != null) {
    container.appendChild(root);
    this.updateContainerStyle(container);
  }
};

/**
 * Function: updateContainerStyle
 *
 * Updates the style of the container after installing the SVG DOM elements.
 */
mxGraphView.prototype.updateContainerStyle = function (container) {
  // Workaround for offset of container
  var style = mxUtils.getCurrentStyle(container);

  if (style != null && style.position == 'static') {
    container.style.position = 'relative';
  }

  // Disables built-in pan and zoom in IE10 and later
  if (mxClient.IS_POINTER) {
    container.style.touchAction = 'none';
  }
};

/**
 * Function: destroy
 *
 * Destroys the view and all its resources.
 */
mxGraphView.prototype.destroy = function () {
  var root = this.canvas != null ? this.canvas.ownerSVGElement : null;

  if (root == null) {
    root = this.canvas;
  }

  if (root != null && root.parentNode != null) {
    this.clear(this.currentRoot, true);
    mxEvent.removeGestureListeners(document, null, this.moveHandler, this.endHandler);
    mxEvent.release(this.graph.container);
    root.parentNode.removeChild(root);

    this.moveHandler = null;
    this.endHandler = null;
    this.canvas = null;
    this.backgroundPane = null;
    this.drawPane = null;
    this.overlayPane = null;
    this.decoratorPane = null;
  }
};

/**
 * Class: mxCurrentRootChange
 *
 * Action to change the current root in a view.
 *
 * Constructor: mxCurrentRootChange
 *
 * Constructs a change of the current root in the given view.
 */
function mxCurrentRootChange(view, root) {
  this.view = view;
  this.root = root;
  this.previous = root;
  this.isUp = root == null;

  if (!this.isUp) {
    var tmp = this.view.currentRoot;
    var model = this.view.graph.getModel();

    while (tmp != null) {
      if (tmp == root) {
        this.isUp = true;
        break;
      }

      tmp = model.getParent(tmp);
    }
  }
}

/**
 * Function: execute
 *
 * Changes the current root of the view.
 */
mxCurrentRootChange.prototype.execute = function () {
  var tmp = this.view.currentRoot;
  this.view.currentRoot = this.previous;
  this.previous = tmp;

  var translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);

  if (translate != null) {
    this.view.translate = new mxPoint(-translate.x, -translate.y);
  }

  if (this.isUp) {
    this.view.clear(this.view.currentRoot, true);
    this.view.validate();
  } else {
    this.view.refresh();
  }

  var name = this.isUp ? mxEvent.UP : mxEvent.DOWN;
  this.view.fireEvent(new mxEventObject(name, 'root', this.view.currentRoot, 'previous', this.previous));
  this.isUp = !this.isUp;
};

/**
 * view/mxGraph.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGraph
 *
 * Extends <mxEventSource> to implement a graph component for
 * the browser. This is the main class of the package. To activate
 * panning and connections use <setPanning> and <setConnectable>.
 * For rubberband selection you must create a new instance of
 * <mxRubberband>. The following listeners are added to
 * <mouseListeners> by default:
 *
 * - <tooltipHandler>: <mxTooltipHandler> that displays tooltips
 * - <panningHandler>: <mxPanningHandler> for panning and popup menus
 * - <connectionHandler>: <mxConnectionHandler> for creating connections
 * - <graphHandler>: <mxGraphHandler> for moving and cloning cells
 *
 * These listeners will be called in the above order if they are enabled.
 *
 * Background Images:
 *
 * To display a background image, set the image, image width and
 * image height using <setBackgroundImage>. If one of the
 * above values has changed then the <view>'s <mxGraphView.validate>
 * should be invoked.
 *
 * Cell Images:
 *
 * To use images in cells, a shape must be specified in the default
 * vertex style (or any named style). Possible shapes are
 * <mxConstants.SHAPE_IMAGE> and <mxConstants.SHAPE_LABEL>.
 * The code to change the shape used in the default vertex style,
 * the following code is used:
 *
 * (code)
 * var style = graph.getStylesheet().getDefaultVertexStyle();
 * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;
 * (end)
 *
 * For the default vertex style, the image to be displayed can be
 * specified in a cell's style using the <mxConstants.STYLE_IMAGE>
 * key and the image URL as a value, for example:
 *
 * (code)
 * image=http://www.example.com/image.gif
 * (end)
 *
 * For a named style, the the stylename must be the first element
 * of the cell style:
 *
 * (code)
 * stylename;image=http://www.example.com/image.gif
 * (end)
 *
 * A cell style can have any number of key=value pairs added, divided
 * by a semicolon as follows:
 *
 * (code)
 * [stylename;|key=value;]
 * (end)
 *
 * Labels:
 *
 * The cell labels are defined by <getLabel> which uses <convertValueToString>
 * if <labelsVisible> is true. If a label must be rendered as HTML markup, then
 * <isHtmlLabel> should return true for the respective cell. If all labels
 * contain HTML markup, <htmlLabels> can be set to true. NOTE: Enabling HTML
 * labels carries a possible security risk (see the section on security in
 * the manual).
 *
 * If wrapping is needed for a label, then <isHtmlLabel> and <isWrapping> must
 * return true for the cell whose label should be wrapped. See <isWrapping> for
 * an example.
 *
 * If clipping is needed to keep the rendering of a HTML label inside the
 * bounds of its vertex, then <isClipping> should return true for the
 * respective cell.
 *
 * By default, edge labels are movable and vertex labels are fixed. This can be
 * changed by setting <edgeLabelsMovable> and <vertexLabelsMovable>, or by
 * overriding <isLabelMovable>.
 *
 * In-place Editing:
 *
 * In-place editing is started with a doubleclick or by typing F2.
 * Programmatically, <edit> is used to check if the cell is editable
 * (<isCellEditable>) and call <startEditingAtCell>, which invokes
 * <mxCellEditor.startEditing>. The editor uses the value returned
 * by <getEditingValue> as the editing value.
 *
 * After in-place editing, <labelChanged> is called, which invokes
 * <mxGraphModel.setValue>, which in turn calls
 * <mxGraphModel.valueForCellChanged> via <mxValueChange>.
 *
 * The event that triggers in-place editing is passed through to the
 * <cellEditor>, which may take special actions depending on the type of the
 * event or mouse location, and is also passed to <getEditingValue>. The event
 * is then passed back to the event processing functions which can perform
 * specific actions based on the trigger event.
 *
 * Tooltips:
 *
 * Tooltips are implemented by <getTooltip>, which calls <getTooltipForCell>
 * if a cell is under the mousepointer. The default implementation checks if
 * the cell has a getTooltip function and calls it if it exists. Hence, in order
 * to provide custom tooltips, the cell must provide a getTooltip function, or
 * one of the two above functions must be overridden.
 *
 * Typically, for custom cell tooltips, the latter function is overridden as
 * follows:
 *
 * (code)
 * graph.getTooltipForCell = function(cell)
 * {
 *   var label = this.convertValueToString(cell);
 *   return 'Tooltip for '+label;
 * }
 * (end)
 *
 * When using a config file, the function is overridden in the mxGraph section
 * using the following entry:
 *
 * (code)
 * <add as="getTooltipForCell"><![CDATA[
 *   function(cell)
 *   {
 *     var label = this.convertValueToString(cell);
 *     return 'Tooltip for '+label;
 *   }
 * ]]></add>
 * (end)
 *
 * "this" refers to the graph in the implementation, so for example to check if
 * a cell is an edge, you use this.getModel().isEdge(cell)
 *
 * For replacing the default implementation of <getTooltipForCell> (rather than
 * replacing the function on a specific instance), the following code should be
 * used after loading the JavaScript files, but before creating a new mxGraph
 * instance using <mxGraph>:
 *
 * (code)
 * mxGraph.prototype.getTooltipForCell = function(cell)
 * {
 *   var label = this.convertValueToString(cell);
 *   return 'Tooltip for '+label;
 * }
 * (end)
 *
 * Shapes & Styles:
 *
 * The implementation of new shapes is demonstrated in the examples. We'll assume
 * that we have implemented a custom shape with the name BoxShape which we want
 * to use for drawing vertices. To use this shape, it must first be registered in
 * the cell renderer as follows:
 *
 * (code)
 * mxCellRenderer.registerShape('box', BoxShape);
 * (end)
 *
 * The code registers the BoxShape constructor under the name box in the cell
 * renderer of the graph. The shape can now be referenced using the shape-key in
 * a style definition. (The cell renderer contains a set of additional shapes,
 * namely one for each constant with a SHAPE-prefix in <mxConstants>.)
 *
 * Styles are a collection of key, value pairs and a stylesheet is a collection
 * of named styles. The names are referenced by the cellstyle, which is stored
 * in <mxCell.style> with the following format: [stylename;|key=value;]. The
 * string is resolved to a collection of key, value pairs, where the keys are
 * overridden with the values in the string.
 *
 * When introducing a new shape, the name under which the shape is registered
 * must be used in the stylesheet. There are three ways of doing this:
 *
 *   - By changing the default style, so that all vertices will use the new
 * 		shape
 *   - By defining a new style, so that only vertices with the respective
 * 		cellstyle will use the new shape
 *   - By using shape=box in the cellstyle's optional list of key, value pairs
 * 		to be overridden
 *
 * In the first case, the code to fetch and modify the default style for
 * vertices is as follows:
 *
 * (code)
 * var style = graph.getStylesheet().getDefaultVertexStyle();
 * style[mxConstants.STYLE_SHAPE] = 'box';
 * (end)
 *
 * The code takes the default vertex style, which is used for all vertices that
 * do not have a specific cellstyle, and modifies the value for the shape-key
 * in-place to use the new BoxShape for drawing vertices. This is done by
 * assigning the box value in the second line, which refers to the name of the
 * BoxShape in the cell renderer.
 *
 * In the second case, a collection of key, value pairs is created and then
 * added to the stylesheet under a new name. In order to distinguish the
 * shapename and the stylename we'll use boxstyle for the stylename:
 *
 * (code)
 * var style = new Object();
 * style[mxConstants.STYLE_SHAPE] = 'box';
 * style[mxConstants.STYLE_STROKECOLOR] = '#000000';
 * style[mxConstants.STYLE_FONTCOLOR] = '#000000';
 * graph.getStylesheet().putCellStyle('boxstyle', style);
 * (end)
 *
 * The code adds a new style with the name boxstyle to the stylesheet. To use
 * this style with a cell, it must be referenced from the cellstyle as follows:
 *
 * (code)
 * var vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20,
 * 				'boxstyle');
 * (end)
 *
 * To summarize, each new shape must be registered in the <mxCellRenderer> with
 * a unique name. That name is then used as the value of the shape-key in a
 * default or custom style. If there are multiple custom shapes, then there
 * should be a separate style for each shape.
 *
 * Inheriting Styles:
 *
 * For fill-, stroke-, gradient-, font- and indicatorColors special keywords
 * can be used. The inherit keyword for one of these colors will inherit the
 * color for the same key from the parent cell. The swimlane keyword does the
 * same, but inherits from the nearest swimlane in the ancestor hierarchy.
 * Finally, the indicated keyword will use the color of the indicator as the
 * color for the given key.
 *
 * Scrollbars:
 *
 * The <containers> overflow CSS property defines if scrollbars are used to
 * display the graph. For values of 'auto' or 'scroll', the scrollbars will
 * be shown. Note that the <resizeContainer> flag is normally not used
 * together with scrollbars, as it will resize the container to match the
 * size of the graph after each change.
 *
 * Multiplicities and Validation:
 *
 * To control the possible connections in mxGraph, <getEdgeValidationError> is
 * used. The default implementation of the function uses <multiplicities>,
 * which is an array of <mxMultiplicity>. Using this class allows to establish
 * simple multiplicities, which are enforced by the graph.
 *
 * The <mxMultiplicity> uses <mxCell.is> to determine for which terminals it
 * applies. The default implementation of <mxCell.is> works with DOM nodes (XML
 * nodes) and checks if the given type parameter matches the nodeName of the
 * node (case insensitive). Optionally, an attributename and value can be
 * specified which are also checked.
 *
 * <getEdgeValidationError> is called whenever the connectivity of an edge
 * changes. It returns an empty string or an error message if the edge is
 * invalid or null if the edge is valid. If the returned string is not empty
 * then it is displayed as an error message.
 *
 * <mxMultiplicity> allows to specify the multiplicity between a terminal and
 * its possible neighbors. For example, if any rectangle may only be connected
 * to, say, a maximum of two circles you can add the following rule to
 * <multiplicities>:
 *
 * (code)
 * graph.multiplicities.push(new mxMultiplicity(
 *   true, 'rectangle', null, null, 0, 2, ['circle'],
 *   'Only 2 targets allowed',
 *   'Only shape targets allowed'));
 * (end)
 *
 * This will display the first error message whenever a rectangle is connected
 * to more than two circles and the second error message if a rectangle is
 * connected to anything but a circle.
 *
 * For certain multiplicities, such as a minimum of 1 connection, which cannot
 * be enforced at cell creation time (unless the cell is created together with
 * the connection), mxGraph offers <validate> which checks all multiplicities
 * for all cells and displays the respective error messages in an overlay icon
 * on the cells.
 *
 * If a cell is collapsed and contains validation errors, a respective warning
 * icon is attached to the collapsed cell.
 *
 * Auto-Layout:
 *
 * For automatic layout, the <getLayout> hook is provided in <mxLayoutManager>.
 * It can be overridden to return a layout algorithm for the children of a
 * given cell.
 *
 * Unconnected edges:
 *
 * The default values for all switches are designed to meet the requirements of
 * general diagram drawing applications. A very typical set of settings to
 * avoid edges that are not connected is the following:
 *
 * (code)
 * graph.setAllowDanglingEdges(false);
 * graph.setDisconnectOnMove(false);
 * (end)
 *
 * Setting the <cloneInvalidEdges> switch to true is optional. This switch
 * controls if edges are inserted after a copy, paste or clone-drag if they are
 * invalid. For example, edges are invalid if copied or control-dragged without
 * having selected the corresponding terminals and allowDanglingEdges is
 * false, in which case the edges will not be cloned if the switch is false.
 *
 * Output:
 *
 * To produce an XML representation for a diagram, the following code can be
 * used.
 *
 * (code)
 * var enc = new mxCodec(mxUtils.createXmlDocument());
 * var node = enc.encode(graph.getModel());
 * (end)
 *
 * This will produce an XML node than can be handled using the DOM API or
 * turned into a string representation using the following code:
 *
 * (code)
 * var xml = mxUtils.getXml(node);
 * (end)
 *
 * To obtain a formatted string, mxUtils.getPrettyXml can be used instead.
 *
 * This string can now be stored in a local persistent storage (for example
 * using Google Gears) or it can be passed to a backend using mxUtils.post as
 * follows. The url variable is the URL of the Java servlet, PHP page or HTTP
 * handler, depending on the server.
 *
 * (code)
 * var xmlString = encodeURIComponent(mxUtils.getXml(node));
 * mxUtils.post(url, 'xml='+xmlString, function(req)
 * {
 *   // Process server response using req of type mxXmlRequest
 * });
 * (end)
 *
 * Input:
 *
 * To load an XML representation of a diagram into an existing graph object
 * mxUtils.load can be used as follows. The url variable is the URL of the Java
 * servlet, PHP page or HTTP handler that produces the XML string.
 *
 * (code)
 * var xmlDoc = mxUtils.load(url).getXml();
 * var node = xmlDoc.documentElement;
 * var dec = new mxCodec(node.ownerDocument);
 * dec.decode(node, graph.getModel());
 * (end)
 *
 * For creating a page that loads the client and a diagram using a single
 * request please refer to the deployment examples in the backends.
 *
 * Functional dependencies:
 *
 * (see images/callgraph.png)
 *
 * Resources:
 *
 * resources/graph - Language resources for mxGraph
 *
 * Group: Events
 *
 * Event: mxEvent.ROOT
 *
 * Fires if the root in the model has changed. This event has no properties.
 *
 * Event: mxEvent.ALIGN_CELLS
 *
 * Fires between begin- and endUpdate in <alignCells>. The <code>cells</code>
 * and <code>align</code> properties contain the respective arguments that were
 * passed to <alignCells>.
 *
 * Event: mxEvent.FLIP_EDGE
 *
 * Fires between begin- and endUpdate in <flipEdge>. The <code>edge</code>
 * property contains the edge passed to <flipEdge>.
 *
 * Event: mxEvent.ORDER_CELLS
 *
 * Fires between begin- and endUpdate in <orderCells>. The <code>cells</code>
 * and <code>back</code> properties contain the respective arguments that were
 * passed to <orderCells>.
 *
 * Event: mxEvent.CELLS_ORDERED
 *
 * Fires between begin- and endUpdate in <cellsOrdered>. The <code>cells</code>
 * and <code>back</code> arguments contain the respective arguments that were
 * passed to <cellsOrdered>.
 *
 * Event: mxEvent.GROUP_CELLS
 *
 * Fires between begin- and endUpdate in <groupCells>. The <code>group</code>,
 * <code>cells</code> and <code>border</code> arguments contain the respective
 * arguments that were passed to <groupCells>.
 *
 * Event: mxEvent.UNGROUP_CELLS
 *
 * Fires between begin- and endUpdate in <ungroupCells>. The <code>cells</code>
 * property contains the array of cells that was passed to <ungroupCells>.
 *
 * Event: mxEvent.REMOVE_CELLS_FROM_PARENT
 *
 * Fires between begin- and endUpdate in <removeCellsFromParent>. The
 * <code>cells</code> property contains the array of cells that was passed to
 * <removeCellsFromParent>.
 *
 * Event: mxEvent.ADD_CELLS
 *
 * Fires between begin- and endUpdate in <addCells>. The <code>cells</code>,
 * <code>parent</code>, <code>index</code>, <code>source</code> and
 * <code>target</code> properties contain the respective arguments that were
 * passed to <addCells>.
 *
 * Event: mxEvent.CELLS_ADDED
 *
 * Fires between begin- and endUpdate in <cellsAdded>. The <code>cells</code>,
 * <code>parent</code>, <code>index</code>, <code>source</code>,
 * <code>target</code> and <code>absolute</code> properties contain the
 * respective arguments that were passed to <cellsAdded>.
 *
 * Event: mxEvent.REMOVE_CELLS
 *
 * Fires between begin- and endUpdate in <removeCells>. The <code>cells</code>
 * and <code>includeEdges</code> arguments contain the respective arguments
 * that were passed to <removeCells>.
 *
 * Event: mxEvent.CELLS_REMOVED
 *
 * Fires between begin- and endUpdate in <cellsRemoved>. The <code>cells</code>
 * argument contains the array of cells that was removed.
 *
 * Event: mxEvent.SPLIT_EDGE
 *
 * Fires between begin- and endUpdate in <splitEdge>. The <code>edge</code>
 * property contains the edge to be splitted, the <code>cells</code>,
 * <code>newEdge</code>, <code>dx</code> and <code>dy</code> properties contain
 * the respective arguments that were passed to <splitEdge>.
 *
 * Event: mxEvent.TOGGLE_CELLS
 *
 * Fires between begin- and endUpdate in <toggleCells>. The <code>show</code>,
 * <code>cells</code> and <code>includeEdges</code> properties contain the
 * respective arguments that were passed to <toggleCells>.
 *
 * Event: mxEvent.FOLD_CELLS
 *
 * Fires between begin- and endUpdate in <foldCells>. The
 * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
 * properties contain the respective arguments that were passed to <foldCells>.
 *
 * Event: mxEvent.CELLS_FOLDED
 *
 * Fires between begin- and endUpdate in cellsFolded. The
 * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
 * properties contain the respective arguments that were passed to
 * <cellsFolded>.
 *
 * Event: mxEvent.UPDATE_CELL_SIZE
 *
 * Fires between begin- and endUpdate in <updateCellSize>. The
 * <code>cell</code> and <code>ignoreChildren</code> properties contain the
 * respective arguments that were passed to <updateCellSize>.
 *
 * Event: mxEvent.RESIZE_CELLS
 *
 * Fires between begin- and endUpdate in <resizeCells>. The <code>cells</code>
 * and <code>bounds</code> properties contain the respective arguments that
 * were passed to <resizeCells>.
 *
 * Event: mxEvent.CELLS_RESIZED
 *
 * Fires between begin- and endUpdate in <cellsResized>. The <code>cells</code>
 * and <code>bounds</code> properties contain the respective arguments that
 * were passed to <cellsResized>.
 *
 * Event: mxEvent.MOVE_CELLS
 *
 * Fires between begin- and endUpdate in <moveCells>. The <code>cells</code>,
 * <code>dx</code>, <code>dy</code>, <code>clone</code>, <code>target</code>
 * and <code>event</code> properties contain the respective arguments that
 * were passed to <moveCells>.
 *
 * Event: mxEvent.CELLS_MOVED
 *
 * Fires between begin- and endUpdate in <cellsMoved>. The <code>cells</code>,
 * <code>dx</code>, <code>dy</code> and <code>disconnect</code> properties
 * contain the respective arguments that were passed to <cellsMoved>.
 *
 * Event: mxEvent.CONNECT_CELL
 *
 * Fires between begin- and endUpdate in <connectCell>. The <code>edge</code>,
 * <code>terminal</code> and <code>source</code> properties contain the
 * respective arguments that were passed to <connectCell>.
 *
 * Event: mxEvent.CELL_CONNECTED
 *
 * Fires between begin- and endUpdate in <cellConnected>. The
 * <code>edge</code>, <code>terminal</code> and <code>source</code> properties
 * contain the respective arguments that were passed to <cellConnected>.
 *
 * Event: mxEvent.REFRESH
 *
 * Fires after <refresh> was executed. This event has no properties.
 *
 * Event: mxEvent.CLICK
 *
 * Fires in <click> after a click event. The <code>event</code> property
 * contains the original mouse event and <code>cell</code> property contains
 * the cell under the mouse or null if the background was clicked.
 *
 * Event: mxEvent.DOUBLE_CLICK
 *
 * Fires in <dblClick> after a double click. The <code>event</code> property
 * contains the original mouse event and the <code>cell</code> property
 * contains the cell under the mouse or null if the background was clicked.
 *
 * Event: mxEvent.GESTURE
 *
 * Fires in <fireGestureEvent> after a touch gesture. The <code>event</code>
 * property contains the original gesture end event and the <code>cell</code>
 * property contains the optional cell associated with the gesture.
 *
 * Event: mxEvent.TAP_AND_HOLD
 *
 * Fires in <tapAndHold> if a tap and hold event was detected. The <code>event</code>
 * property contains the initial touch event and the <code>cell</code> property
 * contains the cell under the mouse or null if the background was clicked.
 *
 * Event: mxEvent.FIRE_MOUSE_EVENT
 *
 * Fires in <fireMouseEvent> before the mouse listeners are invoked. The
 * <code>eventName</code> property contains the event name and the
 * <code>event</code> property contains the <mxMouseEvent>.
 *
 * Event: mxEvent.SIZE
 *
 * Fires after <sizeDidChange> was executed. The <code>bounds</code> property
 * contains the new graph bounds.
 *
 * Event: mxEvent.START_EDITING
 *
 * Fires before the in-place editor starts in <startEditingAtCell>. The
 * <code>cell</code> property contains the cell that is being edited and the
 * <code>event</code> property contains the optional event argument that was
 * passed to <startEditingAtCell>.
 *
 * Event: mxEvent.EDITING_STARTED
 *
 * Fires after the in-place editor starts in <startEditingAtCell>. The
 * <code>cell</code> property contains the cell that is being edited and the
 * <code>event</code> property contains the optional event argument that was
 * passed to <startEditingAtCell>.
 *
 * Event: mxEvent.EDITING_STOPPED
 *
 * Fires after the in-place editor stops in <stopEditing>.
 *
 * Event: mxEvent.LABEL_CHANGED
 *
 * Fires between begin- and endUpdate in <cellLabelChanged>. The
 * <code>cell</code> property contains the cell, the <code>value</code>
 * property contains the new value for the cell, the <code>old</code> property
 * contains the old value and the optional <code>event</code> property contains
 * the mouse event that started the edit.
 *
 * Event: mxEvent.ADD_OVERLAY
 *
 * Fires after an overlay is added in <addCellOverlay>. The <code>cell</code>
 * property contains the cell and the <code>overlay</code> property contains
 * the <mxCellOverlay> that was added.
 *
 * Event: mxEvent.REMOVE_OVERLAY
 *
 * Fires after an overlay is removed in <removeCellOverlay> and
 * <removeCellOverlays>. The <code>cell</code> property contains the cell and
 * the <code>overlay</code> property contains the <mxCellOverlay> that was
 * removed.
 *
 * Constructor: mxGraph
 *
 * Constructs a new mxGraph in the specified container. Model is an optional
 * mxGraphModel. If no model is provided, a new mxGraphModel instance is
 * used as the model. The container must have a valid owner document prior
 * to calling this function in Internet Explorer. RenderHint is a string to
 * affect the display performance and rendering in IE, but not in SVG-based
 * browsers. The parameter is mapped to <dialect>, which may
 * be one of <mxConstants.DIALECT_SVG> for SVG-based browsers,
 * <mxConstants.DIALECT_STRICTHTML> for fastest display mode,
 * <mxConstants.DIALECT_PREFERHTML> for faster display mode,
 * <mxConstants.DIALECT_MIXEDHTML> for fast and <mxConstants.DIALECT_VML>
 * for exact display mode (slowest). The dialects are defined in mxConstants.
 * The default values are DIALECT_SVG for SVG-based browsers and
 * DIALECT_MIXED for IE.
 *
 * The possible values for the renderingHint parameter are explained below:
 *
 * fast - The parameter is based on the fact that the display performance is
 * highly improved in IE if the VML is not contained within a VML group
 * element. The lack of a group element only slightly affects the display while
 * panning, but improves the performance by almost a factor of 2, while keeping
 * the display sufficiently accurate. This also allows to render certain shapes as HTML
 * if the display accuracy is not affected, which is implemented by
 * <mxShape.isMixedModeHtml>. This is the default setting and is mapped to
 * DIALECT_MIXEDHTML.
 * faster - Same as fast, but more expensive shapes are avoided. This is
 * controlled by <mxShape.preferModeHtml>. The default implementation will
 * avoid gradients and rounded rectangles, but more significant shapes, such
 * as rhombus, ellipse, actor and cylinder will be rendered accurately. This
 * setting is mapped to DIALECT_PREFERHTML.
 * fastest - Almost anything will be rendered in Html. This allows for
 * rectangles, labels and images. This setting is mapped to
 * DIALECT_STRICTHTML.
 * exact - If accurate panning is required and if the diagram is small (up
 * to 100 cells), then this value should be used. In this mode, a group is
 * created that contains the VML. This allows for accurate panning and is
 * mapped to DIALECT_VML.
 *
 * Example:
 *
 * To create a graph inside a DOM node with an id of graph:
 * (code)
 * var container = document.getElementById('graph');
 * var graph = new mxGraph(container);
 * (end)
 *
 * Parameters:
 *
 * container - Optional DOM node that acts as a container for the graph.
 * If this is null then the container can be initialized later using
 * <init>.
 * model - Optional <mxGraphModel> that constitutes the graph data.
 * renderHint - Optional string that specifies the display accuracy and
 * performance. Default is mxConstants.DIALECT_MIXEDHTML (for IE).
 * stylesheet - Optional <mxStylesheet> to be used in the graph.
 */
function mxGraph(container, model, renderHint, stylesheet) {
  // Initializes the variable in case the prototype has been
  // modified to hold some listeners (which is possible because
  // the createHandlers call is executed regardless of the
  // arguments passed into the ctor).
  this.mouseListeners = null;

  // Converts the renderHint into a dialect
  this.renderHint = renderHint;

  if (mxClient.IS_SVG) {
    this.dialect = mxConstants.DIALECT_SVG;
  } else if (renderHint == mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML) {
    this.dialect = mxConstants.DIALECT_VML;
  } else if (renderHint == mxConstants.RENDERING_HINT_FASTEST) {
    this.dialect = mxConstants.DIALECT_STRICTHTML;
  } else if (renderHint == mxConstants.RENDERING_HINT_FASTER) {
    this.dialect = mxConstants.DIALECT_PREFERHTML;
  } // default for VML
  else {
    this.dialect = mxConstants.DIALECT_MIXEDHTML;
  }

  // Initializes the main members that do not require a container
  this.model = model != null ? model : new mxGraphModel();
  this.multiplicities = [];
  this.imageBundles = [];
  this.cellRenderer = this.createCellRenderer();
  this.setSelectionModel(this.createSelectionModel());
  this.setStylesheet(stylesheet != null ? stylesheet : this.createStylesheet());
  this.view = this.createGraphView();

  // Adds a graph model listener to update the view
  this.graphModelChangeListener = mxUtils.bind(this, function (sender, evt) {
    this.graphModelChanged(evt.getProperty('edit').changes);
  });

  this.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener);

  // Installs basic event handlers with disabled default settings.
  this.createHandlers();

  // Initializes the display if a container was specified
  if (container != null) {
    this.init(container);
  }

  this.view.revalidate();
}

/**
 * Installs the required language resources at class
 * loading time.
 */
if (mxLoadResources) {
  mxResources.add(mxClient.basePath + '/resources/graph');
} else {
  mxClient.defaultBundles.push(mxClient.basePath + '/resources/graph');
}

/**
 * Extends mxEventSource.
 */
mxGraph.prototype = new mxEventSource();
mxGraph.prototype.constructor = mxGraph;

/**
 * Group: Variables
 */

/**
 * Variable: mouseListeners
 *
 * Holds the mouse event listeners. See <fireMouseEvent>.
 */
mxGraph.prototype.mouseListeners = null;

/**
 * Variable: isMouseDown
 *
 * Holds the state of the mouse button.
 */
mxGraph.prototype.isMouseDown = false;

/**
 * Variable: model
 *
 * Holds the <mxGraphModel> that contains the cells to be displayed.
 */
mxGraph.prototype.model = null;

/**
 * Variable: view
 *
 * Holds the <mxGraphView> that caches the <mxCellStates> for the cells.
 */
mxGraph.prototype.view = null;

/**
 * Variable: stylesheet
 *
 * Holds the <mxStylesheet> that defines the appearance of the cells.
 *
 *
 * Example:
 *
 * Use the following code to read a stylesheet into an existing graph.
 *
 * (code)
 * var req = mxUtils.load('stylesheet.xml');
 * var root = req.getDocumentElement();
 * var dec = new mxCodec(root.ownerDocument);
 * dec.decode(root, graph.stylesheet);
 * (end)
 */
mxGraph.prototype.stylesheet = null;

/**
 * Variable: selectionModel
 *
 * Holds the <mxGraphSelectionModel> that models the current selection.
 */
mxGraph.prototype.selectionModel = null;

/**
 * Variable: cellEditor
 *
 * Holds the <mxCellEditor> that is used as the in-place editing.
 */
mxGraph.prototype.cellEditor = null;

/**
 * Variable: cellRenderer
 *
 * Holds the <mxCellRenderer> for rendering the cells in the graph.
 */
mxGraph.prototype.cellRenderer = null;

/**
 * Variable: multiplicities
 *
 * An array of <mxMultiplicities> describing the allowed
 * connections in a graph.
 */
mxGraph.prototype.multiplicities = null;

/**
 * Variable: renderHint
 *
 * RenderHint as it was passed to the constructor.
 */
mxGraph.prototype.renderHint = null;

/**
 * Variable: dialect
 *
 * Dialect to be used for drawing the graph. Possible values are all
 * constants in <mxConstants> with a DIALECT-prefix.
 */
mxGraph.prototype.dialect = null;

/**
 * Variable: gridSize
 *
 * Specifies the grid size. Default is 10.
 */
mxGraph.prototype.gridSize = 10;

/**
 * Variable: gridEnabled
 *
 * Specifies if the grid is enabled. This is used in <snap>. Default is
 * true.
 */
mxGraph.prototype.gridEnabled = true;

/**
 * Variable: portsEnabled
 *
 * Specifies if ports are enabled. This is used in <cellConnected> to update
 * the respective style. Default is true.
 */
mxGraph.prototype.portsEnabled = true;

/**
 * Variable: nativeDoubleClickEnabled
 *
 * Specifies if native double click events should be detected. Default is true.
 */
mxGraph.prototype.nativeDblClickEnabled = true;

/**
 * Variable: doubleTapEnabled
 *
 * Specifies if double taps on touch-based devices should be handled as a
 * double click. Default is true.
 */
mxGraph.prototype.doubleTapEnabled = true;

/**
 * Variable: doubleTapTimeout
 *
 * Specifies the timeout for double taps and non-native double clicks. Default
 * is 500 ms.
 */
mxGraph.prototype.doubleTapTimeout = 500;

/**
 * Variable: doubleTapTolerance
 *
 * Specifies the tolerance for double taps and double clicks in quirks mode.
 * Default is 25 pixels.
 */
mxGraph.prototype.doubleTapTolerance = 25;

/**
 * Variable: lastTouchX
 *
 * Holds the x-coordinate of the last touch event for double tap detection.
 */
mxGraph.prototype.lastTouchY = 0;

/**
 * Variable: lastTouchX
 *
 * Holds the y-coordinate of the last touch event for double tap detection.
 */
mxGraph.prototype.lastTouchY = 0;

/**
 * Variable: lastTouchTime
 *
 * Holds the time of the last touch event for double click detection.
 */
mxGraph.prototype.lastTouchTime = 0;

/**
 * Variable: tapAndHoldEnabled
 *
 * Specifies if tap and hold should be used for starting connections on touch-based
 * devices. Default is true.
 */
mxGraph.prototype.tapAndHoldEnabled = true;

/**
 * Variable: tapAndHoldDelay
 *
 * Specifies the time for a tap and hold. Default is 500 ms.
 */
mxGraph.prototype.tapAndHoldDelay = 500;

/**
 * Variable: tapAndHoldInProgress
 *
 * True if the timer for tap and hold events is running.
 */
mxGraph.prototype.tapAndHoldInProgress = false;

/**
 * Variable: tapAndHoldValid
 *
 * True as long as the timer is running and the touch events
 * stay within the given <tapAndHoldTolerance>.
 */
mxGraph.prototype.tapAndHoldValid = false;

/**
 * Variable: initialTouchX
 *
 * Holds the x-coordinate of the intial touch event for tap and hold.
 */
mxGraph.prototype.initialTouchX = 0;

/**
 * Variable: initialTouchY
 *
 * Holds the y-coordinate of the intial touch event for tap and hold.
 */
mxGraph.prototype.initialTouchY = 0;

/**
 * Variable: tolerance
 *
 * Tolerance for a move to be handled as a single click.
 * Default is 4 pixels.
 */
mxGraph.prototype.tolerance = 4;

/**
 * Variable: defaultOverlap
 *
 * Value returned by <getOverlap> if <isAllowOverlapParent> returns
 * true for the given cell. <getOverlap> is used in <constrainChild> if
 * <isConstrainChild> returns true. The value specifies the
 * portion of the child which is allowed to overlap the parent.
 */
mxGraph.prototype.defaultOverlap = 0.5;

/**
 * Variable: defaultParent
 *
 * Specifies the default parent to be used to insert new cells.
 * This is used in <getDefaultParent>. Default is null.
 */
mxGraph.prototype.defaultParent = null;

/**
 * Variable: alternateEdgeStyle
 *
 * Specifies the alternate edge style to be used if the main control point
 * on an edge is being doubleclicked. Default is null.
 */
mxGraph.prototype.alternateEdgeStyle = null;

/**
 * Variable: backgroundImage
 *
 * Specifies the <mxImage> to be returned by <getBackgroundImage>. Default
 * is null.
 *
 * Example:
 *
 * (code)
 * var img = new mxImage('http://www.example.com/maps/examplemap.jpg', 1024, 768);
 * graph.setBackgroundImage(img);
 * graph.view.validate();
 * (end)
 */
mxGraph.prototype.backgroundImage = null;

/**
 * Variable: pageVisible
 *
 * Specifies if the background page should be visible. Default is false.
 * Not yet implemented.
 */
mxGraph.prototype.pageVisible = false;

/**
 * Variable: pageBreaksVisible
 *
 * Specifies if a dashed line should be drawn between multiple pages. Default
 * is false. If you change this value while a graph is being displayed then you
 * should call <sizeDidChange> to force an update of the display.
 */
mxGraph.prototype.pageBreaksVisible = false;

/**
 * Variable: pageBreakColor
 *
 * Specifies the color for page breaks. Default is 'gray'.
 */
mxGraph.prototype.pageBreakColor = 'gray';

/**
 * Variable: pageBreakDashed
 *
 * Specifies the page breaks should be dashed. Default is true.
 */
mxGraph.prototype.pageBreakDashed = true;

/**
 * Variable: minPageBreakDist
 *
 * Specifies the minimum distance for page breaks to be visible. Default is
 * 20 (in pixels).
 */
mxGraph.prototype.minPageBreakDist = 20;

/**
 * Variable: preferPageSize
 *
 * Specifies if the graph size should be rounded to the next page number in
 * <sizeDidChange>. This is only used if the graph container has scrollbars.
 * Default is false.
 */
mxGraph.prototype.preferPageSize = false;

/**
 * Variable: pageFormat
 *
 * Specifies the page format for the background page. Default is
 * <mxConstants.PAGE_FORMAT_A4_PORTRAIT>. This is used as the default in
 * <mxPrintPreview> and for painting the background page if <pageVisible> is
 * true and the pagebreaks if <pageBreaksVisible> is true.
 */
mxGraph.prototype.pageFormat = mxConstants.PAGE_FORMAT_A4_PORTRAIT;

/**
 * Variable: pageScale
 *
 * Specifies the scale of the background page. Default is 1.5.
 * Not yet implemented.
 */
mxGraph.prototype.pageScale = 1.5;

/**
 * Variable: enabled
 *
 * Specifies the return value for <isEnabled>. Default is true.
 */
mxGraph.prototype.enabled = true;

/**
 * Variable: escapeEnabled
 *
 * Specifies if <mxKeyHandler> should invoke <escape> when the escape key
 * is pressed. Default is true.
 */
mxGraph.prototype.escapeEnabled = true;

/**
 * Variable: invokesStopCellEditing
 *
 * If true, when editing is to be stopped by way of selection changing,
 * data in diagram changing or other means stopCellEditing is invoked, and
 * changes are saved. This is implemented in a focus handler in
 * <mxCellEditor>. Default is true.
 */
mxGraph.prototype.invokesStopCellEditing = true;

/**
 * Variable: enterStopsCellEditing
 *
 * If true, pressing the enter key without pressing control or shift will stop
 * editing and accept the new value. This is used in <mxCellEditor> to stop
 * cell editing. Note: You can always use F2 and escape to stop editing.
 * Default is false.
 */
mxGraph.prototype.enterStopsCellEditing = false;

/**
 * Variable: useScrollbarsForPanning
 *
 * Specifies if scrollbars should be used for panning in <panGraph> if
 * any scrollbars are available. If scrollbars are enabled in CSS, but no
 * scrollbars appear because the graph is smaller than the container size,
 * then no panning occurs if this is true. Default is true.
 */
mxGraph.prototype.useScrollbarsForPanning = true;

/**
 * Variable: exportEnabled
 *
 * Specifies the return value for <canExportCell>. Default is true.
 */
mxGraph.prototype.exportEnabled = true;

/**
 * Variable: importEnabled
 *
 * Specifies the return value for <canImportCell>. Default is true.
 */
mxGraph.prototype.importEnabled = true;

/**
 * Variable: cellsLocked
 *
 * Specifies the return value for <isCellLocked>. Default is false.
 */
mxGraph.prototype.cellsLocked = false;

/**
 * Variable: cellsCloneable
 *
 * Specifies the return value for <isCellCloneable>. Default is true.
 */
mxGraph.prototype.cellsCloneable = true;

/**
 * Variable: foldingEnabled
 *
 * Specifies if folding (collapse and expand via an image icon in the graph
 * should be enabled). Default is true.
 */
mxGraph.prototype.foldingEnabled = true;

/**
 * Variable: cellsEditable
 *
 * Specifies the return value for <isCellEditable>. Default is true.
 */
mxGraph.prototype.cellsEditable = true;

/**
 * Variable: cellsDeletable
 *
 * Specifies the return value for <isCellDeletable>. Default is true.
 */
mxGraph.prototype.cellsDeletable = true;

/**
 * Variable: cellsMovable
 *
 * Specifies the return value for <isCellMovable>. Default is true.
 */
mxGraph.prototype.cellsMovable = true;

/**
 * Variable: edgeLabelsMovable
 *
 * Specifies the return value for edges in <isLabelMovable>. Default is true.
 */
mxGraph.prototype.edgeLabelsMovable = true;

/**
 * Variable: vertexLabelsMovable
 *
 * Specifies the return value for vertices in <isLabelMovable>. Default is false.
 */
mxGraph.prototype.vertexLabelsMovable = false;

/**
 * Variable: dropEnabled
 *
 * Specifies the return value for <isDropEnabled>. Default is false.
 */
mxGraph.prototype.dropEnabled = false;

/**
 * Variable: splitEnabled
 *
 * Specifies if dropping onto edges should be enabled. This is ignored if
 * <dropEnabled> is false. If enabled, it will call <splitEdge> to carry
 * out the drop operation. Default is true.
 */
mxGraph.prototype.splitEnabled = true;

/**
 * Variable: cellsResizable
 *
 * Specifies the return value for <isCellResizable>. Default is true.
 */
mxGraph.prototype.cellsResizable = true;

/**
 * Variable: cellsBendable
 *
 * Specifies the return value for <isCellsBendable>. Default is true.
 */
mxGraph.prototype.cellsBendable = true;

/**
 * Variable: cellsSelectable
 *
 * Specifies the return value for <isCellSelectable>. Default is true.
 */
mxGraph.prototype.cellsSelectable = true;

/**
 * Variable: cellsDisconnectable
 *
 * Specifies the return value for <isCellDisconntable>. Default is true.
 */
mxGraph.prototype.cellsDisconnectable = true;

/**
 * Variable: autoSizeCells
 *
 * Specifies if the graph should automatically update the cell size after an
 * edit. This is used in <isAutoSizeCell>. Default is false.
 */
mxGraph.prototype.autoSizeCells = false;

/**
 * Variable: autoSizeCellsOnAdd
 *
 * Specifies if autoSize style should be applied when cells are added. Default is false.
 */
mxGraph.prototype.autoSizeCellsOnAdd = false;

/**
 * Variable: autoScroll
 *
 * Specifies if the graph should automatically scroll if the mouse goes near
 * the container edge while dragging. This is only taken into account if the
 * container has scrollbars. Default is true.
 *
 * If you need this to work without scrollbars then set <ignoreScrollbars> to
 * true. Please consult the <ignoreScrollbars> for details. In general, with
 * no scrollbars, the use of <allowAutoPanning> is recommended.
 */
mxGraph.prototype.autoScroll = true;

/**
 * Variable: ignoreScrollbars
 *
 * Specifies if the graph should automatically scroll regardless of the
 * scrollbars. This will scroll the container using positive values for
 * scroll positions (ie usually only rightwards and downwards). To avoid
 * possible conflicts with panning, set <translateToScrollPosition> to true.
 */
mxGraph.prototype.ignoreScrollbars = false;

/**
 * Variable: translateToScrollPosition
 *
 * Specifies if the graph should automatically convert the current scroll
 * position to a translate in the graph view when a mouseUp event is received.
 * This can be used to avoid conflicts when using <autoScroll> and
 * <ignoreScrollbars> with no scrollbars in the container.
 */
mxGraph.prototype.translateToScrollPosition = false;

/**
 * Variable: timerAutoScroll
 *
 * Specifies if autoscrolling should be carried out via mxPanningManager even
 * if the container has scrollbars. This disables <scrollPointToVisible> and
 * uses <mxPanningManager> instead. If this is true then <autoExtend> is
 * disabled. It should only be used with a scroll buffer or when scollbars
 * are visible and scrollable in all directions. Default is false.
 */
mxGraph.prototype.timerAutoScroll = false;

/**
 * Variable: allowAutoPanning
 *
 * Specifies if panning via <panGraph> should be allowed to implement autoscroll
 * if no scrollbars are available in <scrollPointToVisible>. To enable panning
 * inside the container, near the edge, set <mxPanningManager.border> to a
 * positive value. Default is false.
 */
mxGraph.prototype.allowAutoPanning = false;

/**
 * Variable: autoExtend
 *
 * Specifies if the size of the graph should be automatically extended if the
 * mouse goes near the container edge while dragging. This is only taken into
 * account if the container has scrollbars. Default is true. See <autoScroll>.
 */
mxGraph.prototype.autoExtend = true;

/**
 * Variable: maximumGraphBounds
 *
 * <mxRectangle> that specifies the area in which all cells in the diagram
 * should be placed. Uses in <getMaximumGraphBounds>. Use a width or height of
 * 0 if you only want to give a upper, left corner.
 */
mxGraph.prototype.maximumGraphBounds = null;

/**
 * Variable: minimumGraphSize
 *
 * <mxRectangle> that specifies the minimum size of the graph. This is ignored
 * if the graph container has no scrollbars. Default is null.
 */
mxGraph.prototype.minimumGraphSize = null;

/**
 * Variable: minimumContainerSize
 *
 * <mxRectangle> that specifies the minimum size of the <container> if
 * <resizeContainer> is true.
 */
mxGraph.prototype.minimumContainerSize = null;

/**
 * Variable: maximumContainerSize
 *
 * <mxRectangle> that specifies the maximum size of the container if
 * <resizeContainer> is true.
 */
mxGraph.prototype.maximumContainerSize = null;

/**
 * Variable: resizeContainer
 *
 * Specifies if the container should be resized to the graph size when
 * the graph size has changed. Default is false.
 */
mxGraph.prototype.resizeContainer = false;

/**
 * Variable: border
 *
 * Border to be added to the bottom and right side when the container is
 * being resized after the graph has been changed. Default is 0.
 */
mxGraph.prototype.border = 0;

/**
 * Variable: keepEdgesInForeground
 *
 * Specifies if edges should appear in the foreground regardless of their order
 * in the model. If <keepEdgesInForeground> and <keepEdgesInBackground> are
 * both true then the normal order is applied. Default is false.
 */
mxGraph.prototype.keepEdgesInForeground = false;

/**
 * Variable: keepEdgesInBackground
 *
 * Specifies if edges should appear in the background regardless of their order
 * in the model. If <keepEdgesInForeground> and <keepEdgesInBackground> are
 * both true then the normal order is applied. Default is false.
 */
mxGraph.prototype.keepEdgesInBackground = false;

/**
 * Variable: allowNegativeCoordinates
 *
 * Specifies if negative coordinates for vertices are allowed. Default is true.
 */
mxGraph.prototype.allowNegativeCoordinates = true;

/**
 * Variable: constrainChildren
 *
 * Specifies if a child should be constrained inside the parent bounds after a
 * move or resize of the child. Default is true.
 */
mxGraph.prototype.constrainChildren = true;

/**
 * Variable: constrainRelativeChildren
 *
 * Specifies if child cells with relative geometries should be constrained
 * inside the parent bounds, if <constrainChildren> is true, and/or the
 * <maximumGraphBounds>. Default is false.
 */
mxGraph.prototype.constrainRelativeChildren = false;

/**
 * Variable: extendParents
 *
 * Specifies if a parent should contain the child bounds after a resize of
 * the child. Default is true. This has precedence over <constrainChildren>.
 */
mxGraph.prototype.extendParents = true;

/**
 * Variable: extendParentsOnAdd
 *
 * Specifies if parents should be extended according to the <extendParents>
 * switch if cells are added. Default is true.
 */
mxGraph.prototype.extendParentsOnAdd = true;

/**
 * Variable: extendParentsOnAdd
 *
 * Specifies if parents should be extended according to the <extendParents>
 * switch if cells are added. Default is false for backwards compatiblity.
 */
mxGraph.prototype.extendParentsOnMove = false;

/**
 * Variable: recursiveResize
 *
 * Specifies the return value for <isRecursiveResize>. Default is
 * false for backwards compatiblity.
 */
mxGraph.prototype.recursiveResize = false;

/**
 * Variable: collapseToPreferredSize
 *
 * Specifies if the cell size should be changed to the preferred size when
 * a cell is first collapsed. Default is true.
 */
mxGraph.prototype.collapseToPreferredSize = true;

/**
 * Variable: zoomFactor
 *
 * Specifies the factor used for <zoomIn> and <zoomOut>. Default is 1.2
 * (120%).
 */
mxGraph.prototype.zoomFactor = 1.2;

/**
 * Variable: keepSelectionVisibleOnZoom
 *
 * Specifies if the viewport should automatically contain the selection cells
 * after a zoom operation. Default is false.
 */
mxGraph.prototype.keepSelectionVisibleOnZoom = false;

/**
 * Variable: centerZoom
 *
 * Specifies if the zoom operations should go into the center of the actual
 * diagram rather than going from top, left. Default is true.
 */
mxGraph.prototype.centerZoom = true;

/**
 * Variable: resetViewOnRootChange
 *
 * Specifies if the scale and translate should be reset if the root changes in
 * the model. Default is true.
 */
mxGraph.prototype.resetViewOnRootChange = true;

/**
 * Variable: resetEdgesOnResize
 *
 * Specifies if edge control points should be reset after the resize of a
 * connected cell. Default is false.
 */
mxGraph.prototype.resetEdgesOnResize = false;

/**
 * Variable: resetEdgesOnMove
 *
 * Specifies if edge control points should be reset after the move of a
 * connected cell. Default is false.
 */
mxGraph.prototype.resetEdgesOnMove = false;

/**
 * Variable: resetEdgesOnConnect
 *
 * Specifies if edge control points should be reset after the the edge has been
 * reconnected. Default is true.
 */
mxGraph.prototype.resetEdgesOnConnect = true;

/**
 * Variable: allowLoops
 *
 * Specifies if loops (aka self-references) are allowed. Default is false.
 */
mxGraph.prototype.allowLoops = false;

/**
 * Variable: defaultLoopStyle
 *
 * <mxEdgeStyle> to be used for loops. This is a fallback for loops if the
 * <mxConstants.STYLE_LOOP> is undefined. Default is <mxEdgeStyle.Loop>.
 */
mxGraph.prototype.defaultLoopStyle = mxEdgeStyle.Loop;

/**
 * Variable: multigraph
 *
 * Specifies if multiple edges in the same direction between the same pair of
 * vertices are allowed. Default is true.
 */
mxGraph.prototype.multigraph = true;

/**
 * Variable: connectableEdges
 *
 * Specifies if edges are connectable. Default is false. This overrides the
 * connectable field in edges.
 */
mxGraph.prototype.connectableEdges = false;

/**
 * Variable: allowDanglingEdges
 *
 * Specifies if edges with disconnected terminals are allowed in the graph.
 * Default is true.
 */
mxGraph.prototype.allowDanglingEdges = true;

/**
 * Variable: cloneInvalidEdges
 *
 * Specifies if edges that are cloned should be validated and only inserted
 * if they are valid. Default is true.
 */
mxGraph.prototype.cloneInvalidEdges = false;

/**
 * Variable: disconnectOnMove
 *
 * Specifies if edges should be disconnected from their terminals when they
 * are moved. Default is true.
 */
mxGraph.prototype.disconnectOnMove = true;

/**
 * Variable: labelsVisible
 *
 * Specifies if labels should be visible. This is used in <getLabel>. Default
 * is true.
 */
mxGraph.prototype.labelsVisible = true;

/**
 * Variable: htmlLabels
 *
 * Specifies the return value for <isHtmlLabel>. Default is false.
 */
mxGraph.prototype.htmlLabels = false;

/**
 * Variable: swimlaneSelectionEnabled
 *
 * Specifies if swimlanes should be selectable via the content if the
 * mouse is released. Default is true.
 */
mxGraph.prototype.swimlaneSelectionEnabled = true;

/**
 * Variable: swimlaneNesting
 *
 * Specifies if nesting of swimlanes is allowed. Default is true.
 */
mxGraph.prototype.swimlaneNesting = true;

/**
 * Variable: swimlaneIndicatorColorAttribute
 *
 * The attribute used to find the color for the indicator if the indicator
 * color is set to 'swimlane'. Default is <mxConstants.STYLE_FILLCOLOR>.
 */
mxGraph.prototype.swimlaneIndicatorColorAttribute = mxConstants.STYLE_FILLCOLOR;

/**
 * Variable: imageBundles
 *
 * Holds the list of image bundles.
 */
mxGraph.prototype.imageBundles = null;

/**
 * Variable: minFitScale
 *
 * Specifies the minimum scale to be applied in <fit>. Default is 0.1. Set this
 * to null to allow any value.
 */
mxGraph.prototype.minFitScale = 0.1;

/**
 * Variable: maxFitScale
 *
 * Specifies the maximum scale to be applied in <fit>. Default is 8. Set this
 * to null to allow any value.
 */
mxGraph.prototype.maxFitScale = 8;

/**
 * Variable: panDx
 *
 * Current horizontal panning value. Default is 0.
 */
mxGraph.prototype.panDx = 0;

/**
 * Variable: panDy
 *
 * Current vertical panning value. Default is 0.
 */
mxGraph.prototype.panDy = 0;

/**
 * Variable: collapsedImage
 *
 * Specifies the <mxImage> to indicate a collapsed state.
 * Default value is mxClient.imageBasePath + '/collapsed.gif'
 */
mxGraph.prototype.collapsedImage = new mxImage(mxClient.imageBasePath + '/collapsed.gif', 9, 9);

/**
 * Variable: expandedImage
 *
 * Specifies the <mxImage> to indicate a expanded state.
 * Default value is mxClient.imageBasePath + '/expanded.gif'
 */
mxGraph.prototype.expandedImage = new mxImage(mxClient.imageBasePath + '/expanded.gif', 9, 9);

/**
 * Variable: warningImage
 *
 * Specifies the <mxImage> for the image to be used to display a warning
 * overlay. See <setCellWarning>. Default value is mxClient.imageBasePath +
 * '/warning'.  The extension for the image depends on the platform. It is
 * '.png' on the Mac and '.gif' on all other platforms.
 */
mxGraph.prototype.warningImage = new mxImage(
  mxClient.imageBasePath + '/warning' + (mxClient.IS_MAC ? '.png' : '.gif'),
  16,
  16
);

/**
 * Variable: alreadyConnectedResource
 *
 * Specifies the resource key for the error message to be displayed in
 * non-multigraphs when two vertices are already connected. If the resource
 * for this key does not exist then the value is used as the error message.
 * Default is 'alreadyConnected'.
 */
mxGraph.prototype.alreadyConnectedResource = mxClient.language != 'none' ? 'alreadyConnected' : '';

/**
 * Variable: containsValidationErrorsResource
 *
 * Specifies the resource key for the warning message to be displayed when
 * a collapsed cell contains validation errors. If the resource for this
 * key does not exist then the value is used as the warning message.
 * Default is 'containsValidationErrors'.
 */
mxGraph.prototype.containsValidationErrorsResource = mxClient.language != 'none' ? 'containsValidationErrors' : '';

/**
 * Variable: collapseExpandResource
 *
 * Specifies the resource key for the tooltip on the collapse/expand icon.
 * If the resource for this key does not exist then the value is used as
 * the tooltip. Default is 'collapse-expand'.
 */
mxGraph.prototype.collapseExpandResource = mxClient.language != 'none' ? 'collapse-expand' : '';

/**
 * Function: init
 *
 * Initializes the <container> and creates the respective datastructures.
 *
 * Parameters:
 *
 * container - DOM node that will contain the graph display.
 */
mxGraph.prototype.init = function (container) {
  this.container = container;

  // Initializes the in-place editor
  this.cellEditor = this.createCellEditor();

  // Initializes the container using the view
  this.view.init();

  // Updates the size of the container for the current graph
  this.sizeDidChange();

  // Hides tooltips and resets tooltip timer if mouse leaves container
  mxEvent.addListener(
    container,
    'mouseleave',
    mxUtils.bind(this, function (evt) {
      if (
        this.tooltipHandler != null &&
        this.tooltipHandler.div != null &&
        this.tooltipHandler.div != evt.relatedTarget
      ) {
        this.tooltipHandler.hide();
      }
    })
  );

  // Automatic deallocation of memory
  if (mxClient.IS_IE) {
    mxEvent.addListener(
      window,
      'unload',
      mxUtils.bind(this, function () {
        this.destroy();
      })
    );

    // Disable shift-click for text
    mxEvent.addListener(
      container,
      'selectstart',
      mxUtils.bind(this, function (evt) {
        return this.isEditing() || (!this.isMouseDown && !mxEvent.isShiftDown(evt));
      })
    );
  }

  // Workaround for missing last shape and connect preview in IE8 standards
  // mode if no initial graph displayed or no label for shape defined
  if (document.documentMode == 8) {
    container.insertAdjacentHTML(
      'beforeend',
      '<' + mxClient.VML_PREFIX + ':group' + ' style="DISPLAY: none;"></' + mxClient.VML_PREFIX + ':group>'
    );
  }
};

/**
 * Function: createHandlers
 *
 * Creates the tooltip-, panning-, connection- and graph-handler (in this
 * order). This is called in the constructor before <init> is called.
 */
mxGraph.prototype.createHandlers = function () {
  this.tooltipHandler = this.createTooltipHandler();
  this.tooltipHandler.setEnabled(false);
  this.selectionCellsHandler = this.createSelectionCellsHandler();
  this.connectionHandler = this.createConnectionHandler();
  this.connectionHandler.setEnabled(false);
  this.graphHandler = this.createGraphHandler();
  this.panningHandler = this.createPanningHandler();
  this.panningHandler.panningEnabled = false;
  this.popupMenuHandler = this.createPopupMenuHandler();
};

/**
 * Function: createTooltipHandler
 *
 * Creates and returns a new <mxTooltipHandler> to be used in this graph.
 */
mxGraph.prototype.createTooltipHandler = function () {
  return new mxTooltipHandler(this);
};

/**
 * Function: createSelectionCellsHandler
 *
 * Creates and returns a new <mxTooltipHandler> to be used in this graph.
 */
mxGraph.prototype.createSelectionCellsHandler = function () {
  return new mxSelectionCellsHandler(this);
};

/**
 * Function: createConnectionHandler
 *
 * Creates and returns a new <mxConnectionHandler> to be used in this graph.
 */
mxGraph.prototype.createConnectionHandler = function () {
  return new mxConnectionHandler(this);
};

/**
 * Function: createGraphHandler
 *
 * Creates and returns a new <mxGraphHandler> to be used in this graph.
 */
mxGraph.prototype.createGraphHandler = function () {
  return new mxGraphHandler(this);
};

/**
 * Function: createPanningHandler
 *
 * Creates and returns a new <mxPanningHandler> to be used in this graph.
 */
mxGraph.prototype.createPanningHandler = function () {
  return new mxPanningHandler(this);
};

/**
 * Function: createPopupMenuHandler
 *
 * Creates and returns a new <mxPopupMenuHandler> to be used in this graph.
 */
mxGraph.prototype.createPopupMenuHandler = function () {
  return new mxPopupMenuHandler(this);
};

/**
 * Function: createSelectionModel
 *
 * Creates a new <mxGraphSelectionModel> to be used in this graph.
 */
mxGraph.prototype.createSelectionModel = function () {
  return new mxGraphSelectionModel(this);
};

/**
 * Function: createStylesheet
 *
 * Creates a new <mxGraphSelectionModel> to be used in this graph.
 */
mxGraph.prototype.createStylesheet = function () {
  return new mxStylesheet();
};

/**
 * Function: createGraphView
 *
 * Creates a new <mxGraphView> to be used in this graph.
 */
mxGraph.prototype.createGraphView = function () {
  return new mxGraphView(this);
};

/**
 * Function: createCellRenderer
 *
 * Creates a new <mxCellRenderer> to be used in this graph.
 */
mxGraph.prototype.createCellRenderer = function () {
  return new mxCellRenderer();
};

/**
 * Function: createCellEditor
 *
 * Creates a new <mxCellEditor> to be used in this graph.
 */
mxGraph.prototype.createCellEditor = function () {
  return new mxCellEditor(this);
};

/**
 * Function: getModel
 *
 * Returns the <mxGraphModel> that contains the cells.
 */
mxGraph.prototype.getModel = function () {
  return this.model;
};

/**
 * Function: getView
 *
 * Returns the <mxGraphView> that contains the <mxCellStates>.
 */
mxGraph.prototype.getView = function () {
  return this.view;
};

/**
 * Function: getStylesheet
 *
 * Returns the <mxStylesheet> that defines the style.
 */
mxGraph.prototype.getStylesheet = function () {
  return this.stylesheet;
};

/**
 * Function: setStylesheet
 *
 * Sets the <mxStylesheet> that defines the style.
 */
mxGraph.prototype.setStylesheet = function (stylesheet) {
  this.stylesheet = stylesheet;
};

/**
 * Function: getSelectionModel
 *
 * Returns the <mxGraphSelectionModel> that contains the selection.
 */
mxGraph.prototype.getSelectionModel = function () {
  return this.selectionModel;
};

/**
 * Function: setSelectionModel
 *
 * Sets the <mxSelectionModel> that contains the selection.
 */
mxGraph.prototype.setSelectionModel = function (selectionModel) {
  this.selectionModel = selectionModel;
};

/**
 * Function: getSelectionCellsForChanges
 *
 * Returns the cells to be selected for the given array of changes.
 *
 * Parameters:
 *
 * ignoreFn - Optional function that takes a change and returns true if the
 * change should be ignored.
 *
 */
mxGraph.prototype.getSelectionCellsForChanges = function (changes, ignoreFn) {
  var dict = new mxDictionary();
  var cells = [];

  var addCell = mxUtils.bind(this, function (cell) {
    if (!dict.get(cell) && this.model.contains(cell)) {
      if (this.model.isEdge(cell) || this.model.isVertex(cell)) {
        dict.put(cell, true);
        cells.push(cell);
      } else {
        var childCount = this.model.getChildCount(cell);

        for (var i = 0; i < childCount; i++) {
          addCell(this.model.getChildAt(cell, i));
        }
      }
    }
  });

  for (var i = 0; i < changes.length; i++) {
    var change = changes[i];

    if (change.constructor != mxRootChange && (ignoreFn == null || !ignoreFn(change))) {
      var cell = null;

      if (change instanceof mxChildChange) {
        cell = change.child;
      } else if (change.cell != null && change.cell instanceof mxCell) {
        cell = change.cell;
      }

      if (cell != null) {
        addCell(cell);
      }
    }
  }

  return cells;
};

/**
 * Function: graphModelChanged
 *
 * Called when the graph model changes. Invokes <processChange> on each
 * item of the given array to update the view accordingly.
 *
 * Parameters:
 *
 * changes - Array that contains the individual changes.
 */
mxGraph.prototype.graphModelChanged = function (changes) {
  for (var i = 0; i < changes.length; i++) {
    this.processChange(changes[i]);
  }

  this.updateSelection();
  this.view.validate();
  this.sizeDidChange();
};

/**
 * Function: updateSelection
 *
 * Removes selection cells that are not in the model from the selection.
 */
mxGraph.prototype.updateSelection = function () {
  var cells = this.getSelectionCells();
  var removed = [];

  for (var i = 0; i < cells.length; i++) {
    if (!this.model.contains(cells[i]) || !this.isCellVisible(cells[i])) {
      removed.push(cells[i]);
    } else {
      var par = this.model.getParent(cells[i]);

      while (par != null && par != this.view.currentRoot) {
        if (this.isCellCollapsed(par) || !this.isCellVisible(par)) {
          removed.push(cells[i]);
          break;
        }

        par = this.model.getParent(par);
      }
    }
  }

  this.removeSelectionCells(removed);
};

/**
 * Function: processChange
 *
 * Processes the given change and invalidates the respective cached data
 * in <view>. This fires a <root> event if the root has changed in the
 * model.
 *
 * Parameters:
 *
 * change - Object that represents the change on the model.
 */
mxGraph.prototype.processChange = function (change) {
  // Resets the view settings, removes all cells and clears
  // the selection if the root changes.
  if (change instanceof mxRootChange) {
    this.clearSelection();
    this.setDefaultParent(null);
    this.removeStateForCell(change.previous);

    if (this.resetViewOnRootChange) {
      this.view.scale = 1;
      this.view.translate.x = 0;
      this.view.translate.y = 0;
    }

    this.fireEvent(new mxEventObject(mxEvent.ROOT));
  }

  // Adds or removes a child to the view by online invaliding
  // the minimal required portions of the cache, namely, the
  // old and new parent and the child.
  else if (change instanceof mxChildChange) {
    var newParent = this.model.getParent(change.child);
    this.view.invalidate(change.child, true, true);

    if (!this.model.contains(newParent) || this.isCellCollapsed(newParent)) {
      this.view.invalidate(change.child, true, true);
      this.removeStateForCell(change.child);

      // Handles special case of current root of view being removed
      if (this.view.currentRoot == change.child) {
        this.home();
      }
    }

    if (newParent != change.previous) {
      // Refreshes the collapse/expand icons on the parents
      if (newParent != null) {
        this.view.invalidate(newParent, false, false);
      }

      if (change.previous != null) {
        this.view.invalidate(change.previous, false, false);
      }
    }
  }

  // Handles two special cases where the shape does not need to be
  // recreated from scratch, it only needs to be invalidated.
  else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange) {
    // Checks if the geometry has changed to avoid unnessecary revalidation
    if (
      change instanceof mxTerminalChange ||
      (change.previous == null && change.geometry != null) ||
      (change.previous != null && !change.previous.equals(change.geometry))
    ) {
      this.view.invalidate(change.cell);
    }
  }

  // Handles two special cases where only the shape, but no
  // descendants need to be recreated
  else if (change instanceof mxValueChange) {
    this.view.invalidate(change.cell, false, false);
  }

  // Requires a new mxShape in JavaScript
  else if (change instanceof mxStyleChange) {
    this.view.invalidate(change.cell, true, true);
    var state = this.view.getState(change.cell);

    if (state != null) {
      state.invalidStyle = true;
    }
  }

  // Removes the state from the cache by default
  else if (change.cell != null && change.cell instanceof mxCell) {
    this.removeStateForCell(change.cell);
  }
};

/**
 * Function: removeStateForCell
 *
 * Removes all cached information for the given cell and its descendants.
 * This is called when a cell was removed from the model.
 *
 * Paramters:
 *
 * cell - <mxCell> that was removed from the model.
 */
mxGraph.prototype.removeStateForCell = function (cell) {
  var childCount = this.model.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    this.removeStateForCell(this.model.getChildAt(cell, i));
  }

  this.view.invalidate(cell, false, true);
  this.view.removeState(cell);
};

/**
 * Group: Overlays
 */

/**
 * Function: addCellOverlay
 *
 * Adds an <mxCellOverlay> for the specified cell. This method fires an
 * <addoverlay> event and returns the new <mxCellOverlay>.
 *
 * Parameters:
 *
 * cell - <mxCell> to add the overlay for.
 * overlay - <mxCellOverlay> to be added for the cell.
 */
mxGraph.prototype.addCellOverlay = function (cell, overlay) {
  if (cell.overlays == null) {
    cell.overlays = [];
  }

  cell.overlays.push(overlay);

  var state = this.view.getState(cell);

  // Immediately updates the cell display if the state exists
  if (state != null) {
    this.cellRenderer.redraw(state);
  }

  this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY, 'cell', cell, 'overlay', overlay));

  return overlay;
};

/**
 * Function: getCellOverlays
 *
 * Returns the array of <mxCellOverlays> for the given cell or null, if
 * no overlays are defined.
 *
 * Parameters:
 *
 * cell - <mxCell> whose overlays should be returned.
 */
mxGraph.prototype.getCellOverlays = function (cell) {
  return cell.overlays;
};

/**
 * Function: removeCellOverlay
 *
 * Removes and returns the given <mxCellOverlay> from the given cell. This
 * method fires a <removeoverlay> event. If no overlay is given, then all
 * overlays are removed using <removeOverlays>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose overlay should be removed.
 * overlay - Optional <mxCellOverlay> to be removed.
 */
mxGraph.prototype.removeCellOverlay = function (cell, overlay) {
  if (overlay == null) {
    this.removeCellOverlays(cell);
  } else {
    var index = mxUtils.indexOf(cell.overlays, overlay);

    if (index >= 0) {
      cell.overlays.splice(index, 1);

      if (cell.overlays.length == 0) {
        cell.overlays = null;
      }

      // Immediately updates the cell display if the state exists
      var state = this.view.getState(cell);

      if (state != null) {
        this.cellRenderer.redraw(state);
      }

      this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY, 'cell', cell, 'overlay', overlay));
    } else {
      overlay = null;
    }
  }

  return overlay;
};

/**
 * Function: removeCellOverlays
 *
 * Removes all <mxCellOverlays> from the given cell. This method
 * fires a <removeoverlay> event for each <mxCellOverlay> and returns
 * the array of <mxCellOverlays> that was removed from the cell.
 *
 * Parameters:
 *
 * cell - <mxCell> whose overlays should be removed
 */
mxGraph.prototype.removeCellOverlays = function (cell) {
  var overlays = cell.overlays;

  if (overlays != null) {
    cell.overlays = null;

    // Immediately updates the cell display if the state exists
    var state = this.view.getState(cell);

    if (state != null) {
      this.cellRenderer.redraw(state);
    }

    for (var i = 0; i < overlays.length; i++) {
      this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY, 'cell', cell, 'overlay', overlays[i]));
    }
  }

  return overlays;
};

/**
 * Function: clearCellOverlays
 *
 * Removes all <mxCellOverlays> in the graph for the given cell and all its
 * descendants. If no cell is specified then all overlays are removed from
 * the graph. This implementation uses <removeCellOverlays> to remove the
 * overlays from the individual cells.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> that represents the root of the subtree to
 * remove the overlays from. Default is the root in the model.
 */
mxGraph.prototype.clearCellOverlays = function (cell) {
  cell = cell != null ? cell : this.model.getRoot();
  this.removeCellOverlays(cell);

  // Recursively removes all overlays from the children
  var childCount = this.model.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    var child = this.model.getChildAt(cell, i);
    this.clearCellOverlays(child); // recurse
  }
};

/**
 * Function: setCellWarning
 *
 * Creates an overlay for the given cell using the warning and image or
 * <warningImage> and returns the new <mxCellOverlay>. The warning is
 * displayed as a tooltip in a red font and may contain HTML markup. If
 * the warning is null or a zero length string, then all overlays are
 * removed from the cell.
 *
 * Example:
 *
 * (code)
 * graph.setCellWarning(cell, '<b>Warning:</b>: Hello, World!');
 * (end)
 *
 * Parameters:
 *
 * cell - <mxCell> whose warning should be set.
 * warning - String that represents the warning to be displayed.
 * img - Optional <mxImage> to be used for the overlay. Default is
 * <warningImage>.
 * isSelect - Optional boolean indicating if a click on the overlay
 * should select the corresponding cell. Default is false.
 */
mxGraph.prototype.setCellWarning = function (cell, warning, img, isSelect) {
  if (warning != null && warning.length > 0) {
    img = img != null ? img : this.warningImage;

    // Creates the overlay with the image and warning
    var overlay = new mxCellOverlay(img, '<font color=red>' + warning + '</font>');

    // Adds a handler for single mouseclicks to select the cell
    if (isSelect) {
      overlay.addListener(
        mxEvent.CLICK,
        mxUtils.bind(this, function (sender, evt) {
          if (this.isEnabled()) {
            this.setSelectionCell(cell);
          }
        })
      );
    }

    // Sets and returns the overlay in the graph
    return this.addCellOverlay(cell, overlay);
  } else {
    this.removeCellOverlays(cell);
  }

  return null;
};

/**
 * Group: In-place editing
 */

/**
 * Function: startEditing
 *
 * Calls <startEditingAtCell> using the given cell or the first selection
 * cell.
 *
 * Parameters:
 *
 * evt - Optional mouse event that triggered the editing.
 */
mxGraph.prototype.startEditing = function (evt) {
  this.startEditingAtCell(null, evt);
};

/**
 * Function: startEditingAtCell
 *
 * Fires a <startEditing> event and invokes <mxCellEditor.startEditing>
 * on <editor>. After editing was started, a <editingStarted> event is
 * fired.
 *
 * Parameters:
 *
 * cell - <mxCell> to start the in-place editor for.
 * evt - Optional mouse event that triggered the editing.
 */
mxGraph.prototype.startEditingAtCell = function (cell, evt) {
  if (evt == null || !mxEvent.isMultiTouchEvent(evt)) {
    if (cell == null) {
      cell = this.getSelectionCell();

      if (cell != null && !this.isCellEditable(cell)) {
        cell = null;
      }
    }

    if (cell != null) {
      this.fireEvent(new mxEventObject(mxEvent.START_EDITING, 'cell', cell, 'event', evt));
      this.cellEditor.startEditing(cell, evt);
      this.fireEvent(new mxEventObject(mxEvent.EDITING_STARTED, 'cell', cell, 'event', evt));
    }
  }
};

/**
 * Function: getEditingValue
 *
 * Returns the initial value for in-place editing. This implementation
 * returns <convertValueToString> for the given cell. If this function is
 * overridden, then <mxGraphModel.valueForCellChanged> should take care
 * of correctly storing the actual new value inside the user object.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the initial editing value should be returned.
 * evt - Optional mouse event that triggered the editor.
 */
mxGraph.prototype.getEditingValue = function (cell, evt) {
  return this.convertValueToString(cell);
};

/**
 * Function: stopEditing
 *
 * Stops the current editing  and fires a <editingStopped> event.
 *
 * Parameters:
 *
 * cancel - Boolean that specifies if the current editing value
 * should be stored.
 */
mxGraph.prototype.stopEditing = function (cancel) {
  this.cellEditor.stopEditing(cancel);
  this.fireEvent(new mxEventObject(mxEvent.EDITING_STOPPED, 'cancel', cancel));
};

/**
 * Function: labelChanged
 *
 * Sets the label of the specified cell to the given value using
 * <cellLabelChanged> and fires <mxEvent.LABEL_CHANGED> while the
 * transaction is in progress. Returns the cell whose label was changed.
 *
 * Parameters:
 *
 * cell - <mxCell> whose label should be changed.
 * value - New label to be assigned.
 * evt - Optional event that triggered the change.
 */
mxGraph.prototype.labelChanged = function (cell, value, evt) {
  this.model.beginUpdate();
  try {
    var old = cell.value;
    this.cellLabelChanged(cell, value, this.isAutoSizeCell(cell));
    this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED, 'cell', cell, 'value', value, 'old', old, 'event', evt));
  } finally {
    this.model.endUpdate();
  }

  return cell;
};

/**
 * Function: cellLabelChanged
 *
 * Sets the new label for a cell. If autoSize is true then
 * <cellSizeUpdated> will be called.
 *
 * In the following example, the function is extended to map changes to
 * attributes in an XML node, as shown in <convertValueToString>.
 * Alternatively, the handling of this can be implemented as shown in
 * <mxGraphModel.valueForCellChanged> without the need to clone the
 * user object.
 *
 * (code)
 * var graphCellLabelChanged = graph.cellLabelChanged;
 * graph.cellLabelChanged = function(cell, newValue, autoSize)
 * {
 * 	// Cloned for correct undo/redo
 * 	var elt = cell.value.cloneNode(true);
 *  elt.setAttribute('label', newValue);
 *
 *  newValue = elt;
 *  graphCellLabelChanged.apply(this, arguments);
 * };
 * (end)
 *
 * Parameters:
 *
 * cell - <mxCell> whose label should be changed.
 * value - New label to be assigned.
 * autoSize - Boolean that specifies if <cellSizeUpdated> should be called.
 */
mxGraph.prototype.cellLabelChanged = function (cell, value, autoSize) {
  this.model.beginUpdate();
  try {
    this.model.setValue(cell, value);

    if (autoSize) {
      this.cellSizeUpdated(cell, false);
    }
  } finally {
    this.model.endUpdate();
  }
};

/**
 * Group: Event processing
 */

/**
 * Function: escape
 *
 * Processes an escape keystroke.
 *
 * Parameters:
 *
 * evt - Mouseevent that represents the keystroke.
 */
mxGraph.prototype.escape = function (evt) {
  this.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));
};

/**
 * Function: click
 *
 * Processes a singleclick on an optional cell and fires a <click> event.
 * The click event is fired initially. If the graph is enabled and the
 * event has not been consumed, then the cell is selected using
 * <selectCellForEvent> or the selection is cleared using
 * <clearSelection>. The events consumed state is set to true if the
 * corresponding <mxMouseEvent> has been consumed.
 *
 * To handle a click event, use the following code.
 *
 * (code)
 * graph.addListener(mxEvent.CLICK, function(sender, evt)
 * {
 *   var e = evt.getProperty('event'); // mouse event
 *   var cell = evt.getProperty('cell'); // cell may be null
 *
 *   if (cell != null)
 *   {
 *     // Do something useful with cell and consume the event
 *     evt.consume();
 *   }
 * });
 * (end)
 *
 * Parameters:
 *
 * me - <mxMouseEvent> that represents the single click.
 */
mxGraph.prototype.click = function (me) {
  var evt = me.getEvent();
  var cell = me.getCell();
  var mxe = new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', cell);

  if (me.isConsumed()) {
    mxe.consume();
  }

  this.fireEvent(mxe);

  if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed()) {
    if (cell != null) {
      if (this.isTransparentClickEvent(evt)) {
        var active = false;

        var tmp = this.getCellAt(
          me.graphX,
          me.graphY,
          null,
          null,
          null,
          mxUtils.bind(this, function (state) {
            var selected = this.isCellSelected(state.cell);
            active = active || selected;

            return !active || selected || (state.cell != cell && this.model.isAncestor(state.cell, cell));
          })
        );

        if (tmp != null) {
          cell = tmp;
        }
      }
    } else if (this.isSwimlaneSelectionEnabled()) {
      cell = this.getSwimlaneAt(me.getGraphX(), me.getGraphY());

      if (cell != null && (!this.isToggleEvent(evt) || !mxEvent.isAltDown(evt))) {
        var temp = cell;
        var swimlanes = [];

        while (temp != null) {
          temp = this.model.getParent(temp);
          var state = this.view.getState(temp);

          if (this.isSwimlane(temp) && state != null) {
            swimlanes.push(temp);
          }
        }

        // Selects ancestors for selected swimlanes
        if (swimlanes.length > 0) {
          swimlanes = swimlanes.reverse();
          swimlanes.splice(0, 0, cell);
          swimlanes.push(cell);

          for (var i = 0; i < swimlanes.length - 1; i++) {
            if (this.isCellSelected(swimlanes[i])) {
              cell = swimlanes[this.isToggleEvent(evt) ? i : i + 1];
            }
          }
        }
      }
    }

    if (cell != null) {
      this.selectCellForEvent(cell, evt);
    } else if (!this.isToggleEvent(evt)) {
      this.clearSelection();
    }
  }
};

/**
 * Function: isSiblingSelected
 *
 * Returns true if any sibling of the given cell is selected.
 */
mxGraph.prototype.isSiblingSelected = function (cell) {
  var model = this.model;
  var parent = model.getParent(cell);
  var childCount = model.getChildCount(parent);

  for (var i = 0; i < childCount; i++) {
    var child = model.getChildAt(parent, i);

    if (cell != child && this.isCellSelected(child)) {
      return true;
    }
  }

  return false;
};

/**
 * Function: dblClick
 *
 * Processes a doubleclick on an optional cell and fires a <dblclick>
 * event. The event is fired initially. If the graph is enabled and the
 * event has not been consumed, then <edit> is called with the given
 * cell. The event is ignored if no cell was specified.
 *
 * Example for overriding this method.
 *
 * (code)
 * graph.dblClick = function(evt, cell)
 * {
 *   var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
 *   this.fireEvent(mxe);
 *
 *   if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
 *   {
 * 	   mxUtils.alert('Hello, World!');
 *     mxe.consume();
 *   }
 * }
 * (end)
 *
 * Example listener for this event.
 *
 * (code)
 * graph.addListener(mxEvent.DOUBLE_CLICK, function(sender, evt)
 * {
 *   var cell = evt.getProperty('cell');
 *   // do something with the cell and consume the
 *   // event to prevent in-place editing from start
 * });
 * (end)
 *
 * Parameters:
 *
 * evt - Mouseevent that represents the doubleclick.
 * cell - Optional <mxCell> under the mousepointer.
 */
mxGraph.prototype.dblClick = function (evt, cell) {
  var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
  this.fireEvent(mxe);

  // Handles the event if it has not been consumed
  if (
    this.isEnabled() &&
    !mxEvent.isConsumed(evt) &&
    !mxe.isConsumed() &&
    cell != null &&
    this.isCellEditable(cell) &&
    !this.isEditing(cell)
  ) {
    this.startEditingAtCell(cell, evt);
    mxEvent.consume(evt);
  }
};

/**
 * Function: tapAndHold
 *
 * Handles the <mxMouseEvent> by highlighting the <mxCellState>.
 *
 * Parameters:
 *
 * me - <mxMouseEvent> that represents the touch event.
 * state - Optional <mxCellState> that is associated with the event.
 */
mxGraph.prototype.tapAndHold = function (me) {
  var evt = me.getEvent();
  var mxe = new mxEventObject(mxEvent.TAP_AND_HOLD, 'event', evt, 'cell', me.getCell());

  // LATER: Check if event should be consumed if me is consumed
  this.fireEvent(mxe);

  if (mxe.isConsumed()) {
    // Resets the state of the panning handler
    this.panningHandler.panningTrigger = false;
  }

  // Handles the event if it has not been consumed
  if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() && this.connectionHandler.isEnabled()) {
    var state = this.view.getState(this.connectionHandler.marker.getCell(me));

    if (state != null) {
      this.connectionHandler.marker.currentColor = this.connectionHandler.marker.validColor;
      this.connectionHandler.marker.markedState = state;
      this.connectionHandler.marker.mark();

      this.connectionHandler.first = new mxPoint(me.getGraphX(), me.getGraphY());
      this.connectionHandler.edgeState = this.connectionHandler.createEdgeState(me);
      this.connectionHandler.previous = state;
      this.connectionHandler.fireEvent(new mxEventObject(mxEvent.START, 'state', this.connectionHandler.previous));
    }
  }
};

/**
 * Function: scrollPointToVisible
 *
 * Scrolls the graph to the given point, extending the graph container if
 * specified.
 */
mxGraph.prototype.scrollPointToVisible = function (x, y, extend, border) {
  if (!this.timerAutoScroll && (this.ignoreScrollbars || mxUtils.hasScrollbars(this.container))) {
    var c = this.container;
    border = border != null ? border : 20;

    if (
      x >= c.scrollLeft &&
      y >= c.scrollTop &&
      x <= c.scrollLeft + c.clientWidth &&
      y <= c.scrollTop + c.clientHeight
    ) {
      var dx = c.scrollLeft + c.clientWidth - x;

      if (dx < border) {
        var old = c.scrollLeft;
        c.scrollLeft += border - dx;

        // Automatically extends the canvas size to the bottom, right
        // if the event is outside of the canvas and the edge of the
        // canvas has been reached. Notes: Needs fix for IE.
        if (extend && old == c.scrollLeft) {
          if (this.dialect == mxConstants.DIALECT_SVG) {
            var root = this.view.getDrawPane().ownerSVGElement;
            var width = this.container.scrollWidth + border - dx;

            // Updates the clipping region. This is an expensive
            // operation that should not be executed too often.
            root.style.width = width + 'px';
          } else {
            var width = Math.max(c.clientWidth, c.scrollWidth) + border - dx;
            var canvas = this.view.getCanvas();
            canvas.style.width = width + 'px';
          }

          c.scrollLeft += border - dx;
        }
      } else {
        dx = x - c.scrollLeft;

        if (dx < border) {
          c.scrollLeft -= border - dx;
        }
      }

      var dy = c.scrollTop + c.clientHeight - y;

      if (dy < border) {
        var old = c.scrollTop;
        c.scrollTop += border - dy;

        if (old == c.scrollTop && extend) {
          if (this.dialect == mxConstants.DIALECT_SVG) {
            var root = this.view.getDrawPane().ownerSVGElement;
            var height = this.container.scrollHeight + border - dy;

            // Updates the clipping region. This is an expensive
            // operation that should not be executed too often.
            root.style.height = height + 'px';
          } else {
            var height = Math.max(c.clientHeight, c.scrollHeight) + border - dy;
            var canvas = this.view.getCanvas();
            canvas.style.height = height + 'px';
          }

          c.scrollTop += border - dy;
        }
      } else {
        dy = y - c.scrollTop;

        if (dy < border) {
          c.scrollTop -= border - dy;
        }
      }
    }
  } else if (this.allowAutoPanning && !this.panningHandler.isActive()) {
    if (this.panningManager == null) {
      this.panningManager = this.createPanningManager();
    }

    this.panningManager.panTo(x + this.panDx, y + this.panDy);
  }
};

/**
 * Function: createPanningManager
 *
 * Creates and returns an <mxPanningManager>.
 */
mxGraph.prototype.createPanningManager = function () {
  return new mxPanningManager(this);
};

/**
 * Function: getBorderSizes
 *
 * Returns the size of the border and padding on all four sides of the
 * container. The left, top, right and bottom borders are stored in the x, y,
 * width and height of the returned <mxRectangle>, respectively.
 */
mxGraph.prototype.getBorderSizes = function () {
  var css = mxUtils.getCurrentStyle(this.container);

  return new mxRectangle(
    mxUtils.parseCssNumber(css.paddingLeft) +
      (css.borderLeftStyle != 'none' ? mxUtils.parseCssNumber(css.borderLeftWidth) : 0),
    mxUtils.parseCssNumber(css.paddingTop) +
      (css.borderTopStyle != 'none' ? mxUtils.parseCssNumber(css.borderTopWidth) : 0),
    mxUtils.parseCssNumber(css.paddingRight) +
      (css.borderRightStyle != 'none' ? mxUtils.parseCssNumber(css.borderRightWidth) : 0),
    mxUtils.parseCssNumber(css.paddingBottom) +
      (css.borderBottomStyle != 'none' ? mxUtils.parseCssNumber(css.borderBottomWidth) : 0)
  );
};

/**
 * Function: getPreferredPageSize
 *
 * Returns the preferred size of the background page if <preferPageSize> is true.
 */
mxGraph.prototype.getPreferredPageSize = function (bounds, width, height) {
  var scale = this.view.scale;
  var tr = this.view.translate;
  var fmt = this.pageFormat;
  var ps = this.pageScale;
  var page = new mxRectangle(0, 0, Math.ceil(fmt.width * ps), Math.ceil(fmt.height * ps));

  var hCount = this.pageBreaksVisible ? Math.ceil(width / page.width) : 1;
  var vCount = this.pageBreaksVisible ? Math.ceil(height / page.height) : 1;

  return new mxRectangle(0, 0, hCount * page.width + 2 + tr.x, vCount * page.height + 2 + tr.y);
};

/**
 * Function: fit
 *
 * Scales the graph such that the complete diagram fits into <container> and
 * returns the current scale in the view. To fit an initial graph prior to
 * rendering, set <mxGraphView.rendering> to false prior to changing the model
 * and execute the following after changing the model.
 *
 * (code)
 * graph.fit();
 * graph.view.rendering = true;
 * graph.refresh();
 * (end)
 *
 * To fit and center the graph, the following code can be used.
 *
 * (code)
 * var margin = 2;
 * var max = 3;
 *
 * var bounds = graph.getGraphBounds();
 * var cw = graph.container.clientWidth - margin;
 * var ch = graph.container.clientHeight - margin;
 * var w = bounds.width / graph.view.scale;
 * var h = bounds.height / graph.view.scale;
 * var s = Math.min(max, Math.min(cw / w, ch / h));
 *
 * graph.view.scaleAndTranslate(s,
 *   (margin + cw - w * s) / (2 * s) - bounds.x / graph.view.scale,
 *   (margin + ch - h * s) / (2 * s) - bounds.y / graph.view.scale);
 * (end)
 *
 * Parameters:
 *
 * border - Optional number that specifies the border. Default is <border>.
 * keepOrigin - Optional boolean that specifies if the translate should be
 * changed. Default is false.
 * margin - Optional margin in pixels. Default is 0.
 * enabled - Optional boolean that specifies if the scale should be set or
 * just returned. Default is true.
 * ignoreWidth - Optional boolean that specifies if the width should be
 * ignored. Default is false.
 * ignoreHeight - Optional boolean that specifies if the height should be
 * ignored. Default is false.
 * maxHeight - Optional maximum height.
 */
mxGraph.prototype.fit = function (border, keepOrigin, margin, enabled, ignoreWidth, ignoreHeight, maxHeight) {
  if (this.container != null) {
    border = border != null ? border : this.getBorder();
    keepOrigin = keepOrigin != null ? keepOrigin : false;
    margin = margin != null ? margin : 0;
    enabled = enabled != null ? enabled : true;
    ignoreWidth = ignoreWidth != null ? ignoreWidth : false;
    ignoreHeight = ignoreHeight != null ? ignoreHeight : false;

    // Adds spacing and border from css
    var cssBorder = this.getBorderSizes();
    var w1 = this.container.offsetWidth - cssBorder.x - cssBorder.width - 1;
    var h1 = maxHeight != null ? maxHeight : this.container.offsetHeight - cssBorder.y - cssBorder.height - 1;
    var bounds = this.view.getGraphBounds();

    if (bounds.width > 0 && bounds.height > 0) {
      if (keepOrigin && bounds.x != null && bounds.y != null) {
        bounds = bounds.clone();
        bounds.width += bounds.x;
        bounds.height += bounds.y;
        bounds.x = 0;
        bounds.y = 0;
      }

      // LATER: Use unscaled bounding boxes to fix rounding errors
      var s = this.view.scale;
      var w2 = bounds.width / s;
      var h2 = bounds.height / s;

      // Fits to the size of the background image if required
      if (this.backgroundImage != null) {
        w2 = Math.max(w2, this.backgroundImage.width - bounds.x / s);
        h2 = Math.max(h2, this.backgroundImage.height - bounds.y / s);
      }

      var b = (keepOrigin ? border : 2 * border) + margin + 1;

      w1 -= b;
      h1 -= b;

      var s2 = ignoreWidth ? h1 / h2 : ignoreHeight ? w1 / w2 : Math.min(w1 / w2, h1 / h2);

      if (this.minFitScale != null) {
        s2 = Math.max(s2, this.minFitScale);
      }

      if (this.maxFitScale != null) {
        s2 = Math.min(s2, this.maxFitScale);
      }

      if (enabled) {
        if (!keepOrigin) {
          if (!mxUtils.hasScrollbars(this.container)) {
            var x0 =
              bounds.x != null ? Math.floor(this.view.translate.x - bounds.x / s + border / s2 + margin / 2) : border;
            var y0 =
              bounds.y != null ? Math.floor(this.view.translate.y - bounds.y / s + border / s2 + margin / 2) : border;

            this.view.scaleAndTranslate(s2, x0, y0);
          } else {
            this.view.setScale(s2);
            var b2 = this.getGraphBounds();

            if (b2.x != null) {
              this.container.scrollLeft = b2.x;
            }

            if (b2.y != null) {
              this.container.scrollTop = b2.y;
            }
          }
        } else if (this.view.scale != s2) {
          this.view.setScale(s2);
        }
      } else {
        return s2;
      }
    }
  }

  return this.view.scale;
};

/**
 * Function: sizeDidChange
 *
 * Called when the size of the graph has changed. This implementation fires
 * a <size> event after updating the clipping region of the SVG element in
 * SVG-bases browsers.
 */
mxGraph.prototype.sizeDidChange = function () {
  var bounds = this.getGraphBounds();

  if (this.container != null) {
    var border = this.getBorder();

    var width = Math.max(0, bounds.x) + bounds.width + 2 * border;
    var height = Math.max(0, bounds.y) + bounds.height + 2 * border;

    if (this.minimumContainerSize != null) {
      width = Math.max(width, this.minimumContainerSize.width);
      height = Math.max(height, this.minimumContainerSize.height);
    }

    if (this.resizeContainer) {
      this.doResizeContainer(width, height);
    }

    if (this.preferPageSize || (!mxClient.IS_IE && this.pageVisible)) {
      var size = this.getPreferredPageSize(bounds, Math.max(1, width), Math.max(1, height));

      if (size != null) {
        width = size.width * this.view.scale;
        height = size.height * this.view.scale;
      }
    }

    if (this.minimumGraphSize != null) {
      width = Math.max(width, this.minimumGraphSize.width * this.view.scale);
      height = Math.max(height, this.minimumGraphSize.height * this.view.scale);
    }

    width = Math.ceil(width);
    height = Math.ceil(height);

    if (this.dialect == mxConstants.DIALECT_SVG) {
      var root = this.view.getDrawPane().ownerSVGElement;

      if (root != null) {
        root.style.minWidth = Math.max(1, width) + 'px';
        root.style.minHeight = Math.max(1, height) + 'px';
        root.style.width = '100%';
        root.style.height = '100%';
      }
    } else {
      if (mxClient.IS_QUIRKS) {
        // Quirks mode does not support minWidth/-Height
        this.view.updateHtmlCanvasSize(Math.max(1, width), Math.max(1, height));
      } else {
        this.view.canvas.style.minWidth = Math.max(1, width) + 'px';
        this.view.canvas.style.minHeight = Math.max(1, height) + 'px';
      }
    }

    this.updatePageBreaks(this.pageBreaksVisible, width, height);
  }

  this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds));
};

/**
 * Function: doResizeContainer
 *
 * Resizes the container for the given graph width and height.
 */
mxGraph.prototype.doResizeContainer = function (width, height) {
  if (this.maximumContainerSize != null) {
    width = Math.min(this.maximumContainerSize.width, width);
    height = Math.min(this.maximumContainerSize.height, height);
  }

  this.container.style.width = Math.ceil(width) + 'px';
  this.container.style.height = Math.ceil(height) + 'px';
};

/**
 * Function: updatePageBreaks
 *
 * Invokes from <sizeDidChange> to redraw the page breaks.
 *
 * Parameters:
 *
 * visible - Boolean that specifies if page breaks should be shown.
 * width - Specifies the width of the container in pixels.
 * height - Specifies the height of the container in pixels.
 */
mxGraph.prototype.updatePageBreaks = function (visible, width, height) {
  var scale = this.view.scale;
  var tr = this.view.translate;
  var fmt = this.pageFormat;
  var ps = scale * this.pageScale;
  var bounds = new mxRectangle(0, 0, fmt.width * ps, fmt.height * ps);

  var gb = mxRectangle.fromRectangle(this.getGraphBounds());
  gb.width = Math.max(1, gb.width);
  gb.height = Math.max(1, gb.height);

  bounds.x = Math.floor((gb.x - tr.x * scale) / bounds.width) * bounds.width + tr.x * scale;
  bounds.y = Math.floor((gb.y - tr.y * scale) / bounds.height) * bounds.height + tr.y * scale;

  gb.width = Math.ceil((gb.width + (gb.x - bounds.x)) / bounds.width) * bounds.width;
  gb.height = Math.ceil((gb.height + (gb.y - bounds.y)) / bounds.height) * bounds.height;

  // Does not show page breaks if the scale is too small
  visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;

  var horizontalCount = visible ? Math.ceil(gb.height / bounds.height) + 1 : 0;
  var verticalCount = visible ? Math.ceil(gb.width / bounds.width) + 1 : 0;
  var right = (verticalCount - 1) * bounds.width;
  var bottom = (horizontalCount - 1) * bounds.height;

  if (this.horizontalPageBreaks == null && horizontalCount > 0) {
    this.horizontalPageBreaks = [];
  }

  if (this.verticalPageBreaks == null && verticalCount > 0) {
    this.verticalPageBreaks = [];
  }

  var drawPageBreaks = mxUtils.bind(this, function (breaks) {
    if (breaks != null) {
      var count = breaks == this.horizontalPageBreaks ? horizontalCount : verticalCount;

      for (var i = 0; i <= count; i++) {
        var pts =
          breaks == this.horizontalPageBreaks
            ? [
                new mxPoint(Math.round(bounds.x), Math.round(bounds.y + i * bounds.height)),
                new mxPoint(Math.round(bounds.x + right), Math.round(bounds.y + i * bounds.height)),
              ]
            : [
                new mxPoint(Math.round(bounds.x + i * bounds.width), Math.round(bounds.y)),
                new mxPoint(Math.round(bounds.x + i * bounds.width), Math.round(bounds.y + bottom)),
              ];

        if (breaks[i] != null) {
          breaks[i].points = pts;
          breaks[i].redraw();
        } else {
          var pageBreak = new mxPolyline(pts, this.pageBreakColor);
          pageBreak.dialect = this.dialect;
          pageBreak.pointerEvents = false;
          pageBreak.isDashed = this.pageBreakDashed;
          pageBreak.init(this.view.backgroundPane);
          pageBreak.redraw();

          breaks[i] = pageBreak;
        }
      }

      for (var i = count; i < breaks.length; i++) {
        breaks[i].destroy();
      }

      breaks.splice(count, breaks.length - count);
    }
  });

  drawPageBreaks(this.horizontalPageBreaks);
  drawPageBreaks(this.verticalPageBreaks);
};

/**
 * Group: Cell styles
 */

/**
 * Function: getCurrentCellStyle
 *
 * Returns the style for the given cell from the cell state, if one exists,
 * or using <getCellStyle>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose style should be returned as an array.
 * ignoreState - Optional boolean that specifies if the cell state should be ignored.
 */
mxGraph.prototype.getCurrentCellStyle = function (cell, ignoreState) {
  var state = ignoreState ? null : this.view.getState(cell);

  return state != null ? state.style : this.getCellStyle(cell);
};

/**
 * Function: getCellStyle
 *
 * Returns an array of key, value pairs representing the cell style for the
 * given cell. If no string is defined in the model that specifies the
 * style, then the default style for the cell is returned or an empty object,
 * if no style can be found. Note: You should try and get the cell state
 * for the given cell and use the cached style in the state before using
 * this method.
 *
 * Parameters:
 *
 * cell - <mxCell> whose style should be returned as an array.
 */
mxGraph.prototype.getCellStyle = function (cell) {
  var stylename = this.model.getStyle(cell);
  var style = null;

  // Gets the default style for the cell
  if (this.model.isEdge(cell)) {
    style = this.stylesheet.getDefaultEdgeStyle();
  } else {
    style = this.stylesheet.getDefaultVertexStyle();
  }

  // Resolves the stylename using the above as the default
  if (stylename != null) {
    style = this.postProcessCellStyle(this.stylesheet.getCellStyle(stylename, style));
  }

  // Returns a non-null value if no style can be found
  if (style == null) {
    style = new Object();
  }

  return style;
};

/**
 * Function: postProcessCellStyle
 *
 * Tries to resolve the value for the image style in the image bundles and
 * turns short data URIs as defined in mxImageBundle to data URIs as
 * defined in RFC 2397 of the IETF.
 */
mxGraph.prototype.postProcessCellStyle = function (style) {
  if (style != null) {
    var key = style[mxConstants.STYLE_IMAGE];
    var image = this.getImageFromBundles(key);

    if (image != null) {
      style[mxConstants.STYLE_IMAGE] = image;
    } else {
      image = key;
    }

    // Converts short data uris to normal data uris
    if (image != null && image.substring(0, 11) == 'data:image/') {
      if (image.substring(0, 20) == 'data:image/svg+xml,<') {
        // Required for FF and IE11
        image = image.substring(0, 19) + encodeURIComponent(image.substring(19));
      } else if (image.substring(0, 22) != 'data:image/svg+xml,%3C') {
        var comma = image.indexOf(',');

        // Adds base64 encoding prefix if needed
        if (comma > 0 && image.substring(comma - 7, comma + 1) != ';base64,') {
          image = image.substring(0, comma) + ';base64,' + image.substring(comma + 1);
        }
      }

      style[mxConstants.STYLE_IMAGE] = image;
    }
  }

  return style;
};

/**
 * Function: setCellStyle
 *
 * Sets the style of the specified cells. If no cells are given, then the
 * selection cells are changed.
 *
 * Parameters:
 *
 * style - String representing the new style of the cells.
 * cells - Optional array of <mxCells> to set the style for. Default is the
 * selection cells.
 */
mxGraph.prototype.setCellStyle = function (style, cells) {
  cells = cells || this.getSelectionCells();

  if (cells != null) {
    this.model.beginUpdate();
    try {
      for (var i = 0; i < cells.length; i++) {
        this.model.setStyle(cells[i], style);
      }
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Function: toggleCellStyle
 *
 * Toggles the boolean value for the given key in the style of the given cell
 * and returns the new value as 0 or 1. If no cell is specified then the
 * selection cell is used.
 *
 * Parameter:
 *
 * key - String representing the key for the boolean value to be toggled.
 * defaultValue - Optional boolean default value if no value is defined.
 * Default is false.
 * cell - Optional <mxCell> whose style should be modified. Default is
 * the selection cell.
 */
mxGraph.prototype.toggleCellStyle = function (key, defaultValue, cell) {
  cell = cell || this.getSelectionCell();

  return this.toggleCellStyles(key, defaultValue, [cell]);
};

/**
 * Function: toggleCellStyles
 *
 * Toggles the boolean value for the given key in the style of the given cells
 * and returns the new value as 0 or 1. If no cells are specified, then the
 * selection cells are used. For example, this can be used to toggle
 * <mxConstants.STYLE_ROUNDED> or any other style with a boolean value.
 *
 * Parameter:
 *
 * key - String representing the key for the boolean value to be toggled.
 * defaultValue - Optional boolean default value if no value is defined.
 * Default is false.
 * cells - Optional array of <mxCells> whose styles should be modified.
 * Default is the selection cells.
 */
mxGraph.prototype.toggleCellStyles = function (key, defaultValue, cells) {
  defaultValue = defaultValue != null ? defaultValue : false;
  cells = cells || this.getSelectionCells();
  var value = null;

  if (cells != null && cells.length > 0) {
    var style = this.getCurrentCellStyle(cells[0]);
    value = mxUtils.getValue(style, key, defaultValue) ? 0 : 1;
    this.setCellStyles(key, value, cells);
  }

  return value;
};

/**
 * Function: setCellStyles
 *
 * Sets the key to value in the styles of the given cells. This will modify
 * the existing cell styles in-place and override any existing assignment
 * for the given key. If no cells are specified, then the selection cells
 * are changed. If no value is specified, then the respective key is
 * removed from the styles.
 *
 * Parameters:
 *
 * key - String representing the key to be assigned.
 * value - String representing the new value for the key.
 * cells - Optional array of <mxCells> to change the style for. Default is
 * the selection cells.
 */
mxGraph.prototype.setCellStyles = function (key, value, cells) {
  cells = cells || this.getSelectionCells();
  mxUtils.setCellStyles(this.model, cells, key, value);
};

/**
 * Function: toggleCellStyleFlags
 *
 * Toggles the given bit for the given key in the styles of the specified
 * cells.
 *
 * Parameters:
 *
 * key - String representing the key to toggle the flag in.
 * flag - Integer that represents the bit to be toggled.
 * cells - Optional array of <mxCells> to change the style for. Default is
 * the selection cells.
 */
mxGraph.prototype.toggleCellStyleFlags = function (key, flag, cells) {
  this.setCellStyleFlags(key, flag, null, cells);
};

/**
 * Function: setCellStyleFlags
 *
 * Sets or toggles the given bit for the given key in the styles of the
 * specified cells.
 *
 * Parameters:
 *
 * key - String representing the key to toggle the flag in.
 * flag - Integer that represents the bit to be toggled.
 * value - Boolean value to be used or null if the value should be toggled.
 * cells - Optional array of <mxCells> to change the style for. Default is
 * the selection cells.
 */
mxGraph.prototype.setCellStyleFlags = function (key, flag, value, cells) {
  cells = cells || this.getSelectionCells();

  if (cells != null && cells.length > 0) {
    if (value == null) {
      var style = this.getCurrentCellStyle(cells[0]);
      var current = parseInt(style[key] || 0);
      value = !((current & flag) == flag);
    }

    mxUtils.setCellStyleFlags(this.model, cells, key, flag, value);
  }
};

/**
 * Group: Cell alignment and orientation
 */

/**
 * Function: alignCells
 *
 * Aligns the given cells vertically or horizontally according to the given
 * alignment using the optional parameter as the coordinate.
 *
 * Parameters:
 *
 * align - Specifies the alignment. Possible values are all constants in
 * mxConstants with an ALIGN prefix.
 * cells - Array of <mxCells> to be aligned.
 * param - Optional coordinate for the alignment.
 */
mxGraph.prototype.alignCells = function (align, cells, param) {
  if (cells == null) {
    cells = this.getSelectionCells();
  }

  if (cells != null && cells.length > 1) {
    // Finds the required coordinate for the alignment
    if (param == null) {
      for (var i = 0; i < cells.length; i++) {
        var state = this.view.getState(cells[i]);

        if (state != null && !this.model.isEdge(cells[i])) {
          if (param == null) {
            if (align == mxConstants.ALIGN_CENTER) {
              param = state.x + state.width / 2;
              break;
            } else if (align == mxConstants.ALIGN_RIGHT) {
              param = state.x + state.width;
            } else if (align == mxConstants.ALIGN_TOP) {
              param = state.y;
            } else if (align == mxConstants.ALIGN_MIDDLE) {
              param = state.y + state.height / 2;
              break;
            } else if (align == mxConstants.ALIGN_BOTTOM) {
              param = state.y + state.height;
            } else {
              param = state.x;
            }
          } else {
            if (align == mxConstants.ALIGN_RIGHT) {
              param = Math.max(param, state.x + state.width);
            } else if (align == mxConstants.ALIGN_TOP) {
              param = Math.min(param, state.y);
            } else if (align == mxConstants.ALIGN_BOTTOM) {
              param = Math.max(param, state.y + state.height);
            } else {
              param = Math.min(param, state.x);
            }
          }
        }
      }
    }

    // Aligns the cells to the coordinate
    if (param != null) {
      var s = this.view.scale;

      this.model.beginUpdate();
      try {
        for (var i = 0; i < cells.length; i++) {
          var state = this.view.getState(cells[i]);

          if (state != null) {
            var geo = this.getCellGeometry(cells[i]);

            if (geo != null && !this.model.isEdge(cells[i])) {
              geo = geo.clone();

              if (align == mxConstants.ALIGN_CENTER) {
                geo.x += (param - state.x - state.width / 2) / s;
              } else if (align == mxConstants.ALIGN_RIGHT) {
                geo.x += (param - state.x - state.width) / s;
              } else if (align == mxConstants.ALIGN_TOP) {
                geo.y += (param - state.y) / s;
              } else if (align == mxConstants.ALIGN_MIDDLE) {
                geo.y += (param - state.y - state.height / 2) / s;
              } else if (align == mxConstants.ALIGN_BOTTOM) {
                geo.y += (param - state.y - state.height) / s;
              } else {
                geo.x += (param - state.x) / s;
              }

              this.resizeCell(cells[i], geo);
            }
          }
        }

        this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS, 'align', align, 'cells', cells));
      } finally {
        this.model.endUpdate();
      }
    }
  }

  return cells;
};

/**
 * Function: flipEdge
 *
 * Toggles the style of the given edge between null (or empty) and
 * <alternateEdgeStyle>. This method fires <mxEvent.FLIP_EDGE> while the
 * transaction is in progress. Returns the edge that was flipped.
 *
 * Here is an example that overrides this implementation to invert the
 * value of <mxConstants.STYLE_ELBOW> without removing any existing styles.
 *
 * (code)
 * graph.flipEdge = function(edge)
 * {
 *   if (edge != null)
 *   {
 *     var style = this.getCurrentCellStyle(edge);
 *     var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
 *         mxConstants.ELBOW_HORIZONTAL);
 *     var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
 *         mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
 *     this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
 *   }
 * };
 * (end)
 *
 * Parameters:
 *
 * edge - <mxCell> whose style should be changed.
 */
mxGraph.prototype.flipEdge = function (edge) {
  if (edge != null && this.alternateEdgeStyle != null) {
    this.model.beginUpdate();
    try {
      var style = this.model.getStyle(edge);

      if (style == null || style.length == 0) {
        this.model.setStyle(edge, this.alternateEdgeStyle);
      } else {
        this.model.setStyle(edge, null);
      }

      // Removes all existing control points
      this.resetEdge(edge);
      this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, 'edge', edge));
    } finally {
      this.model.endUpdate();
    }
  }

  return edge;
};

/**
 * Function: addImageBundle
 *
 * Adds the specified <mxImageBundle>.
 */
mxGraph.prototype.addImageBundle = function (bundle) {
  this.imageBundles.push(bundle);
};

/**
 * Function: removeImageBundle
 *
 * Removes the specified <mxImageBundle>.
 */
mxGraph.prototype.removeImageBundle = function (bundle) {
  var tmp = [];

  for (var i = 0; i < this.imageBundles.length; i++) {
    if (this.imageBundles[i] != bundle) {
      tmp.push(this.imageBundles[i]);
    }
  }

  this.imageBundles = tmp;
};

/**
 * Function: getImageFromBundles
 *
 * Searches all <imageBundles> for the specified key and returns the value
 * for the first match or null if the key is not found.
 */
mxGraph.prototype.getImageFromBundles = function (key) {
  if (key != null) {
    for (var i = 0; i < this.imageBundles.length; i++) {
      var image = this.imageBundles[i].getImage(key);

      if (image != null) {
        return image;
      }
    }
  }

  return null;
};

/**
 * Group: Order
 */

/**
 * Function: orderCells
 *
 * Moves the given cells to the front or back. The change is carried out
 * using <cellsOrdered>. This method fires <mxEvent.ORDER_CELLS> while the
 * transaction is in progress.
 *
 * Parameters:
 *
 * back - Boolean that specifies if the cells should be moved to back.
 * cells - Array of <mxCells> to move to the background. If null is
 * specified then the selection cells are used.
 */
mxGraph.prototype.orderCells = function (back, cells) {
  if (cells == null) {
    cells = mxUtils.sortCells(this.getSelectionCells(), true);
  }

  this.model.beginUpdate();
  try {
    this.cellsOrdered(cells, back);
    this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS, 'back', back, 'cells', cells));
  } finally {
    this.model.endUpdate();
  }

  return cells;
};

/**
 * Function: cellsOrdered
 *
 * Moves the given cells to the front or back. This method fires
 * <mxEvent.CELLS_ORDERED> while the transaction is in progress.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose order should be changed.
 * back - Boolean that specifies if the cells should be moved to back.
 */
mxGraph.prototype.cellsOrdered = function (cells, back) {
  if (cells != null) {
    this.model.beginUpdate();
    try {
      for (var i = 0; i < cells.length; i++) {
        var parent = this.model.getParent(cells[i]);

        if (back) {
          this.model.add(parent, cells[i], i);
        } else {
          this.model.add(parent, cells[i], this.model.getChildCount(parent) - 1);
        }
      }

      this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED, 'back', back, 'cells', cells));
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Group: Grouping
 */

/**
 * Function: groupCells
 *
 * Adds the cells into the given group. The change is carried out using
 * <cellsAdded>, <cellsMoved> and <cellsResized>. This method fires
 * <mxEvent.GROUP_CELLS> while the transaction is in progress. Returns the
 * new group. A group is only created if there is at least one entry in the
 * given array of cells.
 *
 * Parameters:
 *
 * group - <mxCell> that represents the target group. If null is specified
 * then a new group is created using <createGroupCell>.
 * border - Optional integer that specifies the border between the child
 * area and the group bounds. Default is 0.
 * cells - Optional array of <mxCells> to be grouped. If null is specified
 * then the selection cells are used.
 */
mxGraph.prototype.groupCells = function (group, border, cells) {
  if (cells == null) {
    cells = mxUtils.sortCells(this.getSelectionCells(), true);
  }

  cells = this.getCellsForGroup(cells);

  if (group == null) {
    group = this.createGroupCell(cells);
  }

  var bounds = this.getBoundsForGroup(group, cells, border);

  if (cells.length > 1 && bounds != null) {
    // Uses parent of group or previous parent of first child
    var parent = this.model.getParent(group);

    if (parent == null) {
      parent = this.model.getParent(cells[0]);
    }

    this.model.beginUpdate();
    try {
      // Checks if the group has a geometry and
      // creates one if one does not exist
      if (this.getCellGeometry(group) == null) {
        this.model.setGeometry(group, new mxGeometry());
      }

      // Adds the group into the parent
      var index = this.model.getChildCount(parent);
      this.cellsAdded([group], parent, index, null, null, false, false, false);

      // Adds the children into the group and moves
      index = this.model.getChildCount(group);
      this.cellsAdded(cells, group, index, null, null, false, false, false);
      this.cellsMoved(cells, -bounds.x, -bounds.y, false, false, false);

      // Resizes the group
      this.cellsResized([group], [bounds], false);

      this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS, 'group', group, 'border', border, 'cells', cells));
    } finally {
      this.model.endUpdate();
    }
  }

  return group;
};

/**
 * Function: getCellsForGroup
 *
 * Returns the cells with the same parent as the first cell
 * in the given array.
 */
mxGraph.prototype.getCellsForGroup = function (cells) {
  var result = [];

  if (cells != null && cells.length > 0) {
    var parent = this.model.getParent(cells[0]);
    result.push(cells[0]);

    // Filters selection cells with the same parent
    for (var i = 1; i < cells.length; i++) {
      if (this.model.getParent(cells[i]) == parent) {
        result.push(cells[i]);
      }
    }
  }

  return result;
};

/**
 * Function: getBoundsForGroup
 *
 * Returns the bounds to be used for the given group and children.
 */
mxGraph.prototype.getBoundsForGroup = function (group, children, border) {
  var result = this.getBoundingBoxFromGeometry(children, true);

  if (result != null) {
    if (this.isSwimlane(group)) {
      var size = this.getStartSize(group);

      result.x -= size.width;
      result.y -= size.height;
      result.width += size.width;
      result.height += size.height;
    }

    // Adds the border
    if (border != null) {
      result.x -= border;
      result.y -= border;
      result.width += 2 * border;
      result.height += 2 * border;
    }
  }

  return result;
};

/**
 * Function: createGroupCell
 *
 * Hook for creating the group cell to hold the given array of <mxCells> if
 * no group cell was given to the <group> function.
 *
 * The following code can be used to set the style of new group cells.
 *
 * (code)
 * var graphCreateGroupCell = graph.createGroupCell;
 * graph.createGroupCell = function(cells)
 * {
 *   var group = graphCreateGroupCell.apply(this, arguments);
 *   group.setStyle('group');
 *
 *   return group;
 * };
 */
mxGraph.prototype.createGroupCell = function (cells) {
  var group = new mxCell('');
  group.setVertex(true);
  group.setConnectable(false);

  return group;
};

/**
 * Function: ungroupCells
 *
 * Ungroups the given cells by moving the children the children to their
 * parents parent and removing the empty groups. Returns the children that
 * have been removed from the groups.
 *
 * Parameters:
 *
 * cells - Array of cells to be ungrouped. If null is specified then the
 * selection cells are used.
 */
mxGraph.prototype.ungroupCells = function (cells) {
  var result = [];

  if (cells == null) {
    cells = this.getCellsForUngroup();
  }

  if (cells != null && cells.length > 0) {
    this.model.beginUpdate();
    try {
      for (var i = 0; i < cells.length; i++) {
        var children = this.model.getChildren(cells[i]);

        if (children != null && children.length > 0) {
          children = children.slice();
          var parent = this.model.getParent(cells[i]);
          var index = this.model.getChildCount(parent);

          this.cellsAdded(children, parent, index, null, null, true);
          result = result.concat(children);

          // Fix relative child cells
          for (var j = 0; j < children.length; j++) {
            var state = this.view.getState(children[j]);
            var geo = this.getCellGeometry(children[j]);

            if (state != null && geo != null && geo.relative) {
              geo = geo.clone();
              geo.x = state.origin.x;
              geo.y = state.origin.y;
              geo.relative = false;

              this.model.setGeometry(children[j], geo);
            }
          }
        }
      }

      this.removeCellsAfterUngroup(cells);
      this.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS, 'cells', cells));
    } finally {
      this.model.endUpdate();
    }
  }

  return result;
};

/**
 * Function: getCellsForUngroup
 *
 * Returns the selection cells that can be ungrouped.
 */
mxGraph.prototype.getCellsForUngroup = function () {
  var cells = this.getSelectionCells();

  // Finds the cells with children
  var tmp = [];

  for (var i = 0; i < cells.length; i++) {
    if (this.model.isVertex(cells[i]) && this.model.getChildCount(cells[i]) > 0) {
      tmp.push(cells[i]);
    }
  }

  return tmp;
};

/**
 * Function: removeCellsAfterUngroup
 *
 * Hook to remove the groups after <ungroupCells>.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> that were ungrouped.
 */
mxGraph.prototype.removeCellsAfterUngroup = function (cells) {
  this.cellsRemoved(this.addAllEdges(cells));
};

/**
 * Function: removeCellsFromParent
 *
 * Removes the specified cells from their parents and adds them to the
 * default parent. Returns the cells that were removed from their parents.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be removed from their parents.
 */
mxGraph.prototype.removeCellsFromParent = function (cells) {
  if (cells == null) {
    cells = this.getSelectionCells();
  }

  this.model.beginUpdate();
  try {
    var parent = this.getDefaultParent();
    var index = this.model.getChildCount(parent);

    this.cellsAdded(cells, parent, index, null, null, true);
    this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT, 'cells', cells));
  } finally {
    this.model.endUpdate();
  }

  return cells;
};

/**
 * Function: updateGroupBounds
 *
 * Updates the bounds of the given groups to include all children and returns
 * the passed-in cells. Call this with the groups in parent to child order,
 * top-most group first, the cells are processed in reverse order and cells
 * with no children are ignored.
 *
 * Parameters:
 *
 * cells - The groups whose bounds should be updated. If this is null, then
 * the selection cells are used.
 * border - Optional border to be added in the group. Default is 0.
 * moveGroup - Optional boolean that allows the group to be moved. Default
 * is false.
 * topBorder - Optional top border to be added in the group. Default is 0.
 * rightBorder - Optional top border to be added in the group. Default is 0.
 * bottomBorder - Optional top border to be added in the group. Default is 0.
 * leftBorder - Optional top border to be added in the group. Default is 0.
 */
mxGraph.prototype.updateGroupBounds = function (
  cells,
  border,
  moveGroup,
  topBorder,
  rightBorder,
  bottomBorder,
  leftBorder
) {
  if (cells == null) {
    cells = this.getSelectionCells();
  }

  border = border != null ? border : 0;
  moveGroup = moveGroup != null ? moveGroup : false;
  topBorder = topBorder != null ? topBorder : 0;
  rightBorder = rightBorder != null ? rightBorder : 0;
  bottomBorder = bottomBorder != null ? bottomBorder : 0;
  leftBorder = leftBorder != null ? leftBorder : 0;

  this.model.beginUpdate();
  try {
    for (var i = cells.length - 1; i >= 0; i--) {
      var geo = this.getCellGeometry(cells[i]);

      if (geo != null) {
        var children = this.getChildCells(cells[i]);

        if (children != null && children.length > 0) {
          var bounds = this.getBoundingBoxFromGeometry(children, true);

          if (bounds != null && bounds.width > 0 && bounds.height > 0) {
            // Adds the size of the title area for swimlanes
            var size = this.isSwimlane(cells[i]) ? this.getActualStartSize(cells[i], true) : new mxRectangle();
            geo = geo.clone();

            if (moveGroup) {
              geo.x = Math.round(geo.x + bounds.x - border - size.x - leftBorder);
              geo.y = Math.round(geo.y + bounds.y - border - size.y - topBorder);
            }

            geo.width = Math.round(bounds.width + 2 * border + size.x + leftBorder + rightBorder + size.width);
            geo.height = Math.round(bounds.height + 2 * border + size.y + topBorder + bottomBorder + size.height);

            this.model.setGeometry(cells[i], geo);
            this.moveCells(children, border + size.x - bounds.x + leftBorder, border + size.y - bounds.y + topBorder);
          }
        }
      }
    }
  } finally {
    this.model.endUpdate();
  }

  return cells;
};

/**
 * Function: getBoundingBox
 *
 * Returns the bounding box for the given array of <mxCells>. The bounding box for
 * each cell and its descendants is computed using <mxGraphView.getBoundingBox>.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose bounding box should be returned.
 */
mxGraph.prototype.getBoundingBox = function (cells) {
  var result = null;

  if (cells != null && cells.length > 0) {
    for (var i = 0; i < cells.length; i++) {
      if (this.model.isVertex(cells[i]) || this.model.isEdge(cells[i])) {
        var bbox = this.view.getBoundingBox(this.view.getState(cells[i]), true);

        if (bbox != null) {
          if (result == null) {
            result = mxRectangle.fromRectangle(bbox);
          } else {
            result.add(bbox);
          }
        }
      }
    }
  }

  return result;
};

/**
 * Group: Cell cloning, insertion and removal
 */

/**
 * Function: cloneCell
 *
 * Returns the clone for the given cell. Uses <cloneCells>.
 *
 * Parameters:
 *
 * cell - <mxCell> to be cloned.
 * allowInvalidEdges - Optional boolean that specifies if invalid edges
 * should be cloned. Default is true.
 * mapping - Optional mapping for existing clones.
 * keepPosition - Optional boolean indicating if the position of the cells should
 * be updated to reflect the lost parent cell. Default is false.
 */
mxGraph.prototype.cloneCell = function (cell, allowInvalidEdges, mapping, keepPosition) {
  return this.cloneCells([cell], allowInvalidEdges, mapping, keepPosition)[0];
};

/**
 * Function: cloneCells
 *
 * Returns the clones for the given cells. The clones are created recursively
 * using <mxGraphModel.cloneCells>. If the terminal of an edge is not in the
 * given array, then the respective end is assigned a terminal point and the
 * terminal is removed.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be cloned.
 * allowInvalidEdges - Optional boolean that specifies if invalid edges
 * should be cloned. Default is true.
 * mapping - Optional mapping for existing clones.
 * keepPosition - Optional boolean indicating if the position of the cells should
 * be updated to reflect the lost parent cell. Default is false.
 */
mxGraph.prototype.cloneCells = function (cells, allowInvalidEdges, mapping, keepPosition) {
  allowInvalidEdges = allowInvalidEdges != null ? allowInvalidEdges : true;
  var clones = null;

  if (cells != null) {
    // Creates a dictionary for fast lookups
    var dict = new mxDictionary();
    var tmp = [];

    for (var i = 0; i < cells.length; i++) {
      dict.put(cells[i], true);
      tmp.push(cells[i]);
    }

    if (tmp.length > 0) {
      var scale = this.view.scale;
      var trans = this.view.translate;
      clones = this.model.cloneCells(cells, true, mapping);

      for (var i = 0; i < cells.length; i++) {
        if (
          !allowInvalidEdges &&
          this.model.isEdge(clones[i]) &&
          this.getEdgeValidationError(
            clones[i],
            this.model.getTerminal(clones[i], true),
            this.model.getTerminal(clones[i], false)
          ) != null
        ) {
          clones[i] = null;
        } else {
          var g = this.model.getGeometry(clones[i]);

          if (g != null) {
            var state = this.view.getState(cells[i]);
            var pstate = this.view.getState(this.model.getParent(cells[i]));

            if (state != null && pstate != null) {
              var dx = keepPosition ? 0 : pstate.origin.x;
              var dy = keepPosition ? 0 : pstate.origin.y;

              if (this.model.isEdge(clones[i])) {
                var pts = state.absolutePoints;

                if (pts != null) {
                  // Checks if the source is cloned or sets the terminal point
                  var src = this.model.getTerminal(cells[i], true);

                  while (src != null && !dict.get(src)) {
                    src = this.model.getParent(src);
                  }

                  if (src == null && pts[0] != null) {
                    g.setTerminalPoint(new mxPoint(pts[0].x / scale - trans.x, pts[0].y / scale - trans.y), true);
                  }

                  // Checks if the target is cloned or sets the terminal point
                  var trg = this.model.getTerminal(cells[i], false);

                  while (trg != null && !dict.get(trg)) {
                    trg = this.model.getParent(trg);
                  }

                  var n = pts.length - 1;

                  if (trg == null && pts[n] != null) {
                    g.setTerminalPoint(new mxPoint(pts[n].x / scale - trans.x, pts[n].y / scale - trans.y), false);
                  }

                  // Translates the control points
                  var points = g.points;

                  if (points != null) {
                    for (var j = 0; j < points.length; j++) {
                      points[j].x += dx;
                      points[j].y += dy;
                    }
                  }
                }
              } else {
                g.translate(dx, dy);
              }
            }
          }
        }
      }
    } else {
      clones = [];
    }
  }

  return clones;
};

/**
 * Function: insertVertex
 *
 * Adds a new vertex into the given parent <mxCell> using value as the user
 * object and the given coordinates as the <mxGeometry> of the new vertex.
 * The id and style are used for the respective properties of the new
 * <mxCell>, which is returned.
 *
 * When adding new vertices from a mouse event, one should take into
 * account the offset of the graph container and the scale and translation
 * of the view in order to find the correct unscaled, untranslated
 * coordinates using <mxGraph.getPointForEvent> as follows:
 *
 * (code)
 * var pt = graph.getPointForEvent(evt);
 * var parent = graph.getDefaultParent();
 * graph.insertVertex(parent, null,
 * 			'Hello, World!', x, y, 220, 30);
 * (end)
 *
 * For adding image cells, the style parameter can be assigned as
 *
 * (code)
 * stylename;image=imageUrl
 * (end)
 *
 * See <mxGraph> for more information on using images.
 *
 * Parameters:
 *
 * parent - <mxCell> that specifies the parent of the new vertex.
 * id - Optional string that defines the Id of the new vertex.
 * value - Object to be used as the user object.
 * x - Integer that defines the x coordinate of the vertex.
 * y - Integer that defines the y coordinate of the vertex.
 * width - Integer that defines the width of the vertex.
 * height - Integer that defines the height of the vertex.
 * style - Optional string that defines the cell style.
 * relative - Optional boolean that specifies if the geometry is relative.
 * Default is false.
 */
mxGraph.prototype.insertVertex = function (parent, id, value, x, y, width, height, style, relative) {
  var vertex = this.createVertex(parent, id, value, x, y, width, height, style, relative);

  return this.addCell(vertex, parent);
};

/**
 * Function: createVertex
 *
 * Hook method that creates the new vertex for <insertVertex>.
 */
mxGraph.prototype.createVertex = function (parent, id, value, x, y, width, height, style, relative) {
  // Creates the geometry for the vertex
  var geometry = new mxGeometry(x, y, width, height);
  geometry.relative = relative != null ? relative : false;

  // Creates the vertex
  var vertex = new mxCell(value, geometry, style);
  vertex.setId(id);
  vertex.setVertex(true);
  vertex.setConnectable(true);

  return vertex;
};

/**
 * Function: insertEdge
 *
 * Adds a new edge into the given parent <mxCell> using value as the user
 * object and the given source and target as the terminals of the new edge.
 * The id and style are used for the respective properties of the new
 * <mxCell>, which is returned.
 *
 * Parameters:
 *
 * parent - <mxCell> that specifies the parent of the new edge.
 * id - Optional string that defines the Id of the new edge.
 * value - JavaScript object to be used as the user object.
 * source - <mxCell> that defines the source of the edge.
 * target - <mxCell> that defines the target of the edge.
 * style - Optional string that defines the cell style.
 */
mxGraph.prototype.insertEdge = function (parent, id, value, source, target, style) {
  var edge = this.createEdge(parent, id, value, source, target, style);

  return this.addEdge(edge, parent, source, target);
};

/**
 * Function: createEdge
 *
 * Hook method that creates the new edge for <insertEdge>. This
 * implementation does not set the source and target of the edge, these
 * are set when the edge is added to the model.
 *
 */
mxGraph.prototype.createEdge = function (parent, id, value, source, target, style) {
  // Creates the edge
  var edge = new mxCell(value, new mxGeometry(), style);
  edge.setId(id);
  edge.setEdge(true);
  edge.geometry.relative = true;

  return edge;
};

/**
 * Function: addEdge
 *
 * Adds the edge to the parent and connects it to the given source and
 * target terminals. This is a shortcut method. Returns the edge that was
 * added.
 *
 * Parameters:
 *
 * edge - <mxCell> to be inserted into the given parent.
 * parent - <mxCell> that represents the new parent. If no parent is
 * given then the default parent is used.
 * source - Optional <mxCell> that represents the source terminal.
 * target - Optional <mxCell> that represents the target terminal.
 * index - Optional index to insert the cells at. Default is to append.
 */
mxGraph.prototype.addEdge = function (edge, parent, source, target, index) {
  return this.addCell(edge, parent, index, source, target);
};

/**
 * Function: addCell
 *
 * Adds the cell to the parent and connects it to the given source and
 * target terminals. This is a shortcut method. Returns the cell that was
 * added.
 *
 * Parameters:
 *
 * cell - <mxCell> to be inserted into the given parent.
 * parent - <mxCell> that represents the new parent. If no parent is
 * given then the default parent is used.
 * index - Optional index to insert the cells at. Default is to append.
 * source - Optional <mxCell> that represents the source terminal.
 * target - Optional <mxCell> that represents the target terminal.
 */
mxGraph.prototype.addCell = function (cell, parent, index, source, target) {
  return this.addCells([cell], parent, index, source, target)[0];
};

/**
 * Function: addCells
 *
 * Adds the cells to the parent at the given index, connecting each cell to
 * the optional source and target terminal. The change is carried out using
 * <cellsAdded>. This method fires <mxEvent.ADD_CELLS> while the
 * transaction is in progress. Returns the cells that were added.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be inserted.
 * parent - <mxCell> that represents the new parent. If no parent is
 * given then the default parent is used.
 * index - Optional index to insert the cells at. Default is to append.
 * source - Optional source <mxCell> for all inserted cells.
 * target - Optional target <mxCell> for all inserted cells.
 * absolute - Optional boolean indicating of cells should be kept at
 * their absolute position. Default is false.
 */
mxGraph.prototype.addCells = function (cells, parent, index, source, target, absolute) {
  if (parent == null) {
    parent = this.getDefaultParent();
  }

  if (index == null) {
    index = this.model.getChildCount(parent);
  }

  this.model.beginUpdate();
  try {
    this.cellsAdded(cells, parent, index, source, target, absolute != null ? absolute : false, true);
    this.fireEvent(
      new mxEventObject(
        mxEvent.ADD_CELLS,
        'cells',
        cells,
        'parent',
        parent,
        'index',
        index,
        'source',
        source,
        'target',
        target
      )
    );
  } finally {
    this.model.endUpdate();
  }

  return cells;
};

/**
 * Function: cellsAdded
 *
 * Adds the specified cells to the given parent. This method fires
 * <mxEvent.CELLS_ADDED> while the transaction is in progress.
 */
mxGraph.prototype.cellsAdded = function (cells, parent, index, source, target, absolute, constrain, extend) {
  if (cells != null && parent != null && index != null) {
    this.model.beginUpdate();
    try {
      var parentState = absolute ? this.view.getState(parent) : null;
      var o1 = parentState != null ? parentState.origin : null;
      var zero = new mxPoint(0, 0);

      for (var i = 0; i < cells.length; i++) {
        if (cells[i] == null) {
          index--;
        } else {
          var previous = this.model.getParent(cells[i]);

          // Keeps the cell at its absolute location
          if (o1 != null && cells[i] != parent && parent != previous) {
            var oldState = this.view.getState(previous);
            var o2 = oldState != null ? oldState.origin : zero;
            var geo = this.model.getGeometry(cells[i]);

            if (geo != null) {
              var dx = o2.x - o1.x;
              var dy = o2.y - o1.y;

              // FIXME: Cells should always be inserted first before any other edit
              // to avoid forward references in sessions.
              geo = geo.clone();
              geo.translate(dx, dy);

              if (!geo.relative && this.model.isVertex(cells[i]) && !this.isAllowNegativeCoordinates()) {
                geo.x = Math.max(0, geo.x);
                geo.y = Math.max(0, geo.y);
              }

              this.model.setGeometry(cells[i], geo);
            }
          }

          // Decrements all following indices
          // if cell is already in parent
          if (parent == previous && index + i > this.model.getChildCount(parent)) {
            index--;
          }

          this.model.add(parent, cells[i], index + i);

          if (this.autoSizeCellsOnAdd) {
            this.autoSizeCell(cells[i], true);
          }

          // Extends the parent or constrains the child
          if ((extend == null || extend) && this.isExtendParentsOnAdd(cells[i]) && this.isExtendParent(cells[i])) {
            this.extendParent(cells[i]);
          }

          // Additionally constrains the child after extending the parent
          if (constrain == null || constrain) {
            this.constrainChild(cells[i]);
          }

          // Sets the source terminal
          if (source != null) {
            this.cellConnected(cells[i], source, true);
          }

          // Sets the target terminal
          if (target != null) {
            this.cellConnected(cells[i], target, false);
          }
        }
      }

      this.fireEvent(
        new mxEventObject(
          mxEvent.CELLS_ADDED,
          'cells',
          cells,
          'parent',
          parent,
          'index',
          index,
          'source',
          source,
          'target',
          target,
          'absolute',
          absolute
        )
      );
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Function: autoSizeCell
 *
 * Resizes the specified cell to just fit around the its label and/or children
 *
 * Parameters:
 *
 * cell - <mxCells> to be resized.
 * recurse - Optional boolean which specifies if all descendants should be
 * autosized. Default is true.
 */
mxGraph.prototype.autoSizeCell = function (cell, recurse) {
  recurse = recurse != null ? recurse : true;

  if (recurse) {
    var childCount = this.model.getChildCount(cell);

    for (var i = 0; i < childCount; i++) {
      this.autoSizeCell(this.model.getChildAt(cell, i));
    }
  }

  if (this.getModel().isVertex(cell) && this.isAutoSizeCell(cell)) {
    this.updateCellSize(cell);
  }
};

/**
 * Function: removeCells
 *
 * Removes the given cells from the graph including all connected edges if
 * includeEdges is true. The change is carried out using <cellsRemoved>.
 * This method fires <mxEvent.REMOVE_CELLS> while the transaction is in
 * progress. The removed cells are returned as an array.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to remove. If null is specified then the
 * selection cells which are deletable are used.
 * includeEdges - Optional boolean which specifies if all connected edges
 * should be removed as well. Default is true.
 */
mxGraph.prototype.removeCells = function (cells, includeEdges) {
  includeEdges = includeEdges != null ? includeEdges : true;

  if (cells == null) {
    cells = this.getDeletableCells(this.getSelectionCells());
  }

  // Adds all edges to the cells
  if (includeEdges) {
    // FIXME: Remove duplicate cells in result or do not add if
    // in cells or descendant of cells
    cells = this.getDeletableCells(this.addAllEdges(cells));
  } else {
    cells = cells.slice();

    // Removes edges that are currently not
    // visible as those cannot be updated
    var edges = this.getDeletableCells(this.getAllEdges(cells));
    var dict = new mxDictionary();

    for (var i = 0; i < cells.length; i++) {
      dict.put(cells[i], true);
    }

    for (var i = 0; i < edges.length; i++) {
      if (this.view.getState(edges[i]) == null && !dict.get(edges[i])) {
        dict.put(edges[i], true);
        cells.push(edges[i]);
      }
    }
  }

  this.model.beginUpdate();
  try {
    this.cellsRemoved(cells);
    this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS, 'cells', cells, 'includeEdges', includeEdges));
  } finally {
    this.model.endUpdate();
  }

  return cells;
};

/**
 * Function: cellsRemoved
 *
 * Removes the given cells from the model. This method fires
 * <mxEvent.CELLS_REMOVED> while the transaction is in progress.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to remove.
 */
mxGraph.prototype.cellsRemoved = function (cells) {
  if (cells != null && cells.length > 0) {
    var scale = this.view.scale;
    var tr = this.view.translate;

    this.model.beginUpdate();
    try {
      // Creates hashtable for faster lookup
      var dict = new mxDictionary();

      for (var i = 0; i < cells.length; i++) {
        dict.put(cells[i], true);
      }

      for (var i = 0; i < cells.length; i++) {
        // Disconnects edges which are not being removed
        var edges = this.getAllEdges([cells[i]]);

        var disconnectTerminal = mxUtils.bind(this, function (edge, source) {
          var geo = this.model.getGeometry(edge);

          if (geo != null) {
            // Checks if terminal is being removed
            var terminal = this.model.getTerminal(edge, source);
            var connected = false;
            var tmp = terminal;

            while (tmp != null) {
              if (cells[i] == tmp) {
                connected = true;
                break;
              }

              tmp = this.model.getParent(tmp);
            }

            if (connected) {
              geo = geo.clone();
              var state = this.view.getState(edge);

              if (state != null && state.absolutePoints != null) {
                var pts = state.absolutePoints;
                var n = source ? 0 : pts.length - 1;

                geo.setTerminalPoint(
                  new mxPoint(pts[n].x / scale - tr.x - state.origin.x, pts[n].y / scale - tr.y - state.origin.y),
                  source
                );
              } else {
                // Fallback to center of terminal if routing
                // points are not available to add new point
                // KNOWN: Should recurse to find parent offset
                // of edge for nested groups but invisible edges
                // should be removed in removeCells step
                var tstate = this.view.getState(terminal);

                if (tstate != null) {
                  geo.setTerminalPoint(
                    new mxPoint(tstate.getCenterX() / scale - tr.x, tstate.getCenterY() / scale - tr.y),
                    source
                  );
                }
              }

              this.model.setGeometry(edge, geo);
              this.model.setTerminal(edge, null, source);
            }
          }
        });

        for (var j = 0; j < edges.length; j++) {
          if (!dict.get(edges[j])) {
            dict.put(edges[j], true);
            disconnectTerminal(edges[j], true);
            disconnectTerminal(edges[j], false);
          }
        }

        this.model.remove(cells[i]);
      }

      this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED, 'cells', cells));
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Function: splitEdge
 *
 * Splits the given edge by adding the newEdge between the previous source
 * and the given cell and reconnecting the source of the given edge to the
 * given cell. This method fires <mxEvent.SPLIT_EDGE> while the transaction
 * is in progress. Returns the new edge that was inserted.
 *
 * Parameters:
 *
 * edge - <mxCell> that represents the edge to be splitted.
 * cells - <mxCells> that represents the cells to insert into the edge.
 * newEdge - <mxCell> that represents the edge to be inserted.
 * dx - Optional integer that specifies the vector to move the cells.
 * dy - Optional integer that specifies the vector to move the cells.
 * x - Integer that specifies the x-coordinate of the drop location.
 * y - Integer that specifies the y-coordinate of the drop location.
 * parent - Optional parent to insert the cell. If null the parent of
 * the edge is used.
 */
mxGraph.prototype.splitEdge = function (edge, cells, newEdge, dx, dy, x, y, parent) {
  dx = dx || 0;
  dy = dy || 0;

  parent = parent != null ? parent : this.model.getParent(edge);
  var source = this.model.getTerminal(edge, true);

  this.model.beginUpdate();
  try {
    if (newEdge == null) {
      newEdge = this.cloneCell(edge);

      // Removes waypoints before/after new cell
      var state = this.view.getState(edge);
      var geo = this.getCellGeometry(newEdge);

      if (geo != null && geo.points != null && state != null) {
        var t = this.view.translate;
        var s = this.view.scale;
        var idx = mxUtils.findNearestSegment(state, (dx + t.x) * s, (dy + t.y) * s);
        geo.points = geo.points.slice(0, idx);

        geo = this.getCellGeometry(edge);

        if (geo != null && geo.points != null) {
          geo = geo.clone();
          geo.points = geo.points.slice(idx);
          this.model.setGeometry(edge, geo);
        }
      }
    }

    this.cellsMoved(cells, dx, dy, false, false);
    this.cellsAdded(cells, parent, this.model.getChildCount(parent), null, null, true);
    this.cellsAdded([newEdge], parent, this.model.getChildCount(parent), source, cells[0], false);
    this.cellConnected(edge, cells[0], true);
    this.fireEvent(
      new mxEventObject(mxEvent.SPLIT_EDGE, 'edge', edge, 'cells', cells, 'newEdge', newEdge, 'dx', dx, 'dy', dy)
    );
  } finally {
    this.model.endUpdate();
  }

  return newEdge;
};

/**
 * Group: Cell visibility
 */

/**
 * Function: toggleCells
 *
 * Sets the visible state of the specified cells and all connected edges
 * if includeEdges is true. The change is carried out using <cellsToggled>.
 * This method fires <mxEvent.TOGGLE_CELLS> while the transaction is in
 * progress. Returns the cells whose visible state was changed.
 *
 * Parameters:
 *
 * show - Boolean that specifies the visible state to be assigned.
 * cells - Array of <mxCells> whose visible state should be changed. If
 * null is specified then the selection cells are used.
 * includeEdges - Optional boolean indicating if the visible state of all
 * connected edges should be changed as well. Default is true.
 */
mxGraph.prototype.toggleCells = function (show, cells, includeEdges) {
  if (cells == null) {
    cells = this.getSelectionCells();
  }

  // Adds all connected edges recursively
  if (includeEdges) {
    cells = this.addAllEdges(cells);
  }

  this.model.beginUpdate();
  try {
    this.cellsToggled(cells, show);
    this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS, 'show', show, 'cells', cells, 'includeEdges', includeEdges));
  } finally {
    this.model.endUpdate();
  }

  return cells;
};

/**
 * Function: cellsToggled
 *
 * Sets the visible state of the specified cells.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose visible state should be changed.
 * show - Boolean that specifies the visible state to be assigned.
 */
mxGraph.prototype.cellsToggled = function (cells, show) {
  if (cells != null && cells.length > 0) {
    this.model.beginUpdate();
    try {
      for (var i = 0; i < cells.length; i++) {
        this.model.setVisible(cells[i], show);
      }
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Group: Folding
 */

/**
 * Function: foldCells
 *
 * Sets the collapsed state of the specified cells and all descendants
 * if recurse is true. The change is carried out using <cellsFolded>.
 * This method fires <mxEvent.FOLD_CELLS> while the transaction is in
 * progress. Returns the cells whose collapsed state was changed.
 *
 * Parameters:
 *
 * collapsed - Boolean indicating the collapsed state to be assigned.
 * recurse - Optional boolean indicating if the collapsed state of all
 * descendants should be set. Default is false.
 * cells - Array of <mxCells> whose collapsed state should be set. If
 * null is specified then the foldable selection cells are used.
 * checkFoldable - Optional boolean indicating of isCellFoldable should be
 * checked. Default is false.
 * evt - Optional native event that triggered the invocation.
 */
mxGraph.prototype.foldCells = function (collapse, recurse, cells, checkFoldable, evt) {
  recurse = recurse != null ? recurse : false;

  if (cells == null) {
    cells = this.getFoldableCells(this.getSelectionCells(), collapse);
  }

  this.stopEditing(false);

  this.model.beginUpdate();
  try {
    this.cellsFolded(cells, collapse, recurse, checkFoldable);
    this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS, 'collapse', collapse, 'recurse', recurse, 'cells', cells));
  } finally {
    this.model.endUpdate();
  }

  return cells;
};

/**
 * Function: cellsFolded
 *
 * Sets the collapsed state of the specified cells. This method fires
 * <mxEvent.CELLS_FOLDED> while the transaction is in progress. Returns the
 * cells whose collapsed state was changed.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose collapsed state should be set.
 * collapsed - Boolean indicating the collapsed state to be assigned.
 * recurse - Boolean indicating if the collapsed state of all descendants
 * should be set.
 * checkFoldable - Optional boolean indicating of isCellFoldable should be
 * checked. Default is false.
 */
mxGraph.prototype.cellsFolded = function (cells, collapse, recurse, checkFoldable) {
  if (cells != null && cells.length > 0) {
    this.model.beginUpdate();
    try {
      for (var i = 0; i < cells.length; i++) {
        if ((!checkFoldable || this.isCellFoldable(cells[i], collapse)) && collapse != this.isCellCollapsed(cells[i])) {
          this.model.setCollapsed(cells[i], collapse);
          this.swapBounds(cells[i], collapse);

          if (this.isExtendParent(cells[i])) {
            this.extendParent(cells[i]);
          }

          if (recurse) {
            var children = this.model.getChildren(cells[i]);
            this.cellsFolded(children, collapse, recurse);
          }

          this.constrainChild(cells[i]);
        }
      }

      this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED, 'cells', cells, 'collapse', collapse, 'recurse', recurse));
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Function: swapBounds
 *
 * Swaps the alternate and the actual bounds in the geometry of the given
 * cell invoking <updateAlternateBounds> before carrying out the swap.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the bounds should be swapped.
 * willCollapse - Boolean indicating if the cell is going to be collapsed.
 */
mxGraph.prototype.swapBounds = function (cell, willCollapse) {
  if (cell != null) {
    var geo = this.model.getGeometry(cell);

    if (geo != null) {
      geo = geo.clone();

      this.updateAlternateBounds(cell, geo, willCollapse);
      geo.swap();

      this.model.setGeometry(cell, geo);
    }
  }
};

/**
 * Function: updateAlternateBounds
 *
 * Updates or sets the alternate bounds in the given geometry for the given
 * cell depending on whether the cell is going to be collapsed. If no
 * alternate bounds are defined in the geometry and
 * <collapseToPreferredSize> is true, then the preferred size is used for
 * the alternate bounds. The top, left corner is always kept at the same
 * location.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the geometry is being udpated.
 * g - <mxGeometry> for which the alternate bounds should be updated.
 * willCollapse - Boolean indicating if the cell is going to be collapsed.
 */
mxGraph.prototype.updateAlternateBounds = function (cell, geo, willCollapse) {
  if (cell != null && geo != null) {
    var style = this.getCurrentCellStyle(cell);

    if (geo.alternateBounds == null) {
      var bounds = geo;

      if (this.collapseToPreferredSize) {
        var tmp = this.getPreferredSizeForCell(cell);

        if (tmp != null) {
          bounds = tmp;

          var startSize = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE);

          if (startSize > 0) {
            bounds.height = Math.max(bounds.height, startSize);
          }
        }
      }

      geo.alternateBounds = new mxRectangle(0, 0, bounds.width, bounds.height);
    }

    if (geo.alternateBounds != null) {
      geo.alternateBounds.x = geo.x;
      geo.alternateBounds.y = geo.y;

      var alpha = mxUtils.toRadians(style[mxConstants.STYLE_ROTATION] || 0);

      if (alpha != 0) {
        var dx = geo.alternateBounds.getCenterX() - geo.getCenterX();
        var dy = geo.alternateBounds.getCenterY() - geo.getCenterY();

        var cos = Math.cos(alpha);
        var sin = Math.sin(alpha);

        var dx2 = cos * dx - sin * dy;
        var dy2 = sin * dx + cos * dy;

        geo.alternateBounds.x += dx2 - dx;
        geo.alternateBounds.y += dy2 - dy;
      }
    }
  }
};

/**
 * Function: addAllEdges
 *
 * Returns an array with the given cells and all edges that are connected
 * to a cell or one of its descendants.
 */
mxGraph.prototype.addAllEdges = function (cells) {
  var allCells = cells.slice();

  return mxUtils.removeDuplicates(allCells.concat(this.getAllEdges(cells)));
};

/**
 * Function: getAllEdges
 *
 * Returns all edges connected to the given cells or its descendants.
 */
mxGraph.prototype.getAllEdges = function (cells) {
  var edges = [];

  if (cells != null) {
    for (var i = 0; i < cells.length; i++) {
      var edgeCount = this.model.getEdgeCount(cells[i]);

      for (var j = 0; j < edgeCount; j++) {
        edges.push(this.model.getEdgeAt(cells[i], j));
      }

      // Recurses
      var children = this.model.getChildren(cells[i]);
      edges = edges.concat(this.getAllEdges(children));
    }
  }

  return edges;
};

/**
 * Group: Cell sizing
 */

/**
 * Function: updateCellSize
 *
 * Updates the size of the given cell in the model using <cellSizeUpdated>.
 * This method fires <mxEvent.UPDATE_CELL_SIZE> while the transaction is in
 * progress. Returns the cell whose size was updated.
 *
 * Parameters:
 *
 * cell - <mxCell> whose size should be updated.
 */
mxGraph.prototype.updateCellSize = function (cell, ignoreChildren) {
  ignoreChildren = ignoreChildren != null ? ignoreChildren : false;

  this.model.beginUpdate();
  try {
    this.cellSizeUpdated(cell, ignoreChildren);
    this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE, 'cell', cell, 'ignoreChildren', ignoreChildren));
  } finally {
    this.model.endUpdate();
  }

  return cell;
};

/**
 * Function: cellSizeUpdated
 *
 * Updates the size of the given cell in the model using
 * <getPreferredSizeForCell> to get the new size.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the size should be changed.
 */
mxGraph.prototype.cellSizeUpdated = function (cell, ignoreChildren) {
  if (cell != null) {
    this.model.beginUpdate();
    try {
      var size = this.getPreferredSizeForCell(cell);
      var geo = this.model.getGeometry(cell);

      if (size != null && geo != null) {
        var collapsed = this.isCellCollapsed(cell);
        geo = geo.clone();

        if (this.isSwimlane(cell)) {
          var style = this.getCellStyle(cell);
          var cellStyle = this.model.getStyle(cell);

          if (cellStyle == null) {
            cellStyle = '';
          }

          if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true)) {
            cellStyle = mxUtils.setStyle(cellStyle, mxConstants.STYLE_STARTSIZE, size.height + 8);

            if (collapsed) {
              geo.height = size.height + 8;
            }

            geo.width = size.width;
          } else {
            cellStyle = mxUtils.setStyle(cellStyle, mxConstants.STYLE_STARTSIZE, size.width + 8);

            if (collapsed) {
              geo.width = size.width + 8;
            }

            geo.height = size.height;
          }

          this.model.setStyle(cell, cellStyle);
        } else {
          var state = this.view.createState(cell);
          var align = state.style[mxConstants.STYLE_ALIGN] || mxConstants.ALIGN_CENTER;

          if (align == mxConstants.ALIGN_RIGHT) {
            geo.x += geo.width - size.width;
          } else if (align == mxConstants.ALIGN_CENTER) {
            geo.x += Math.round((geo.width - size.width) / 2);
          }

          var valign = this.getVerticalAlign(state);

          if (valign == mxConstants.ALIGN_BOTTOM) {
            geo.y += geo.height - size.height;
          } else if (valign == mxConstants.ALIGN_MIDDLE) {
            geo.y += Math.round((geo.height - size.height) / 2);
          }

          geo.width = size.width;
          geo.height = size.height;
        }

        if (!ignoreChildren && !collapsed) {
          var bounds = this.view.getBounds(this.model.getChildren(cell));

          if (bounds != null) {
            var tr = this.view.translate;
            var scale = this.view.scale;

            var width = (bounds.x + bounds.width) / scale - geo.x - tr.x;
            var height = (bounds.y + bounds.height) / scale - geo.y - tr.y;

            geo.width = Math.max(geo.width, width);
            geo.height = Math.max(geo.height, height);
          }
        }

        this.cellsResized([cell], [geo], false);
      }
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Function: getPreferredSizeForCell
 *
 * Returns the preferred width and height of the given <mxCell> as an
 * <mxRectangle>. To implement a minimum width, add a new style eg.
 * minWidth in the vertex and override this method as follows.
 *
 * (code)
 * var graphGetPreferredSizeForCell = graph.getPreferredSizeForCell;
 * graph.getPreferredSizeForCell = function(cell)
 * {
 *   var result = graphGetPreferredSizeForCell.apply(this, arguments);
 *   var style = this.getCellStyle(cell);
 *
 *   if (style['minWidth'] > 0)
 *   {
 *     result.width = Math.max(style['minWidth'], result.width);
 *   }
 *
 *   return result;
 * };
 * (end)
 *
 * Parameters:
 *
 * cell - <mxCell> for which the preferred size should be returned.
 * textWidth - Optional maximum text width for word wrapping.
 */
mxGraph.prototype.getPreferredSizeForCell = function (cell, textWidth) {
  var result = null;

  if (cell != null) {
    var state = this.view.createState(cell);
    var style = state.style;

    if (!this.model.isEdge(cell)) {
      var fontSize = style[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE;
      var dx = 0;
      var dy = 0;

      // Adds dimension of image if shape is a label
      if (this.getImage(state) != null || style[mxConstants.STYLE_IMAGE] != null) {
        if (style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_LABEL) {
          if (style[mxConstants.STYLE_VERTICAL_ALIGN] == mxConstants.ALIGN_MIDDLE) {
            dx += parseFloat(style[mxConstants.STYLE_IMAGE_WIDTH]) || mxLabel.prototype.imageSize;
          }

          if (style[mxConstants.STYLE_ALIGN] != mxConstants.ALIGN_CENTER) {
            dy += parseFloat(style[mxConstants.STYLE_IMAGE_HEIGHT]) || mxLabel.prototype.imageSize;
          }
        }
      }

      // Adds spacings
      dx += 2 * (style[mxConstants.STYLE_SPACING] || 0);
      dx += style[mxConstants.STYLE_SPACING_LEFT] || 0;
      dx += style[mxConstants.STYLE_SPACING_RIGHT] || 0;

      dy += 2 * (style[mxConstants.STYLE_SPACING] || 0);
      dy += style[mxConstants.STYLE_SPACING_TOP] || 0;
      dy += style[mxConstants.STYLE_SPACING_BOTTOM] || 0;

      // Add spacing for collapse/expand icon
      // LATER: Check alignment and use constants
      // for image spacing
      var image = this.getFoldingImage(state);

      if (image != null) {
        dx += image.width + 8;
      }

      // Adds space for label
      var value = this.cellRenderer.getLabelValue(state);

      if (value != null && value.length > 0) {
        if (!this.isHtmlLabel(state.cell)) {
          value = mxUtils.htmlEntities(value, false);
        }

        value = value.replace(/\n/g, '<br>');

        var size = mxUtils.getSizeForString(
          value,
          fontSize,
          style[mxConstants.STYLE_FONTFAMILY],
          textWidth,
          style[mxConstants.STYLE_FONTSTYLE]
        );
        var width = size.width + dx;
        var height = size.height + dy;

        if (!mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true)) {
          var tmp = height;

          height = width;
          width = tmp;
        }

        if (this.gridEnabled) {
          width = this.snap(width + this.gridSize / 2);
          height = this.snap(height + this.gridSize / 2);
        }

        result = new mxRectangle(0, 0, width, height);
      } else {
        var gs2 = 4 * this.gridSize;
        result = new mxRectangle(0, 0, gs2, gs2);
      }
    }
  }

  return result;
};

/**
 * Function: resizeCell
 *
 * Sets the bounds of the given cell using <resizeCells>. Returns the
 * cell which was passed to the function.
 *
 * Parameters:
 *
 * cell - <mxCell> whose bounds should be changed.
 * bounds - <mxRectangle> that represents the new bounds.
 */
mxGraph.prototype.resizeCell = function (cell, bounds, recurse) {
  return this.resizeCells([cell], [bounds], recurse)[0];
};

/**
 * Function: resizeCells
 *
 * Sets the bounds of the given cells and fires a <mxEvent.RESIZE_CELLS>
 * event while the transaction is in progress. Returns the cells which
 * have been passed to the function.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose bounds should be changed.
 * bounds - Array of <mxRectangles> that represent the new bounds.
 */
mxGraph.prototype.resizeCells = function (cells, bounds, recurse) {
  recurse = recurse != null ? recurse : this.isRecursiveResize();

  this.model.beginUpdate();
  try {
    var prev = this.cellsResized(cells, bounds, recurse);
    this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS, 'cells', cells, 'bounds', bounds, 'previous', prev));
  } finally {
    this.model.endUpdate();
  }

  return cells;
};

/**
 * Function: cellsResized
 *
 * Sets the bounds of the given cells and fires a <mxEvent.CELLS_RESIZED>
 * event. If <extendParents> is true, then the parent is extended if a
 * child size is changed so that it overlaps with the parent.
 *
 * The following example shows how to control group resizes to make sure
 * that all child cells stay within the group.
 *
 * (code)
 * graph.addListener(mxEvent.CELLS_RESIZED, function(sender, evt)
 * {
 *   var cells = evt.getProperty('cells');
 *
 *   if (cells != null)
 *   {
 *     for (var i = 0; i < cells.length; i++)
 *     {
 *       if (graph.getModel().getChildCount(cells[i]) > 0)
 *       {
 *         var geo = graph.getCellGeometry(cells[i]);
 *
 *         if (geo != null)
 *         {
 *           var children = graph.getChildCells(cells[i], true, true);
 *           var bounds = graph.getBoundingBoxFromGeometry(children, true);
 *
 *           geo = geo.clone();
 *           geo.width = Math.max(geo.width, bounds.width);
 *           geo.height = Math.max(geo.height, bounds.height);
 *
 *           graph.getModel().setGeometry(cells[i], geo);
 *         }
 *       }
 *     }
 *   }
 * });
 * (end)
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose bounds should be changed.
 * bounds - Array of <mxRectangles> that represent the new bounds.
 * recurse - Optional boolean that specifies if the children should be resized.
 */
mxGraph.prototype.cellsResized = function (cells, bounds, recurse) {
  recurse = recurse != null ? recurse : false;
  var prev = [];

  if (cells != null && bounds != null && cells.length == bounds.length) {
    this.model.beginUpdate();
    try {
      for (var i = 0; i < cells.length; i++) {
        prev.push(this.cellResized(cells[i], bounds[i], false, recurse));

        if (this.isExtendParent(cells[i])) {
          this.extendParent(cells[i]);
        }

        this.constrainChild(cells[i]);
      }

      if (this.resetEdgesOnResize) {
        this.resetEdges(cells);
      }

      this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED, 'cells', cells, 'bounds', bounds, 'previous', prev));
    } finally {
      this.model.endUpdate();
    }
  }

  return prev;
};

/**
 * Function: cellResized
 *
 * Resizes the parents recursively so that they contain the complete area
 * of the resized child cell.
 *
 * Parameters:
 *
 * cell - <mxCell> whose bounds should be changed.
 * bounds - <mxRectangles> that represent the new bounds.
 * ignoreRelative - Boolean that indicates if relative cells should be ignored.
 * recurse - Optional boolean that specifies if the children should be resized.
 */
mxGraph.prototype.cellResized = function (cell, bounds, ignoreRelative, recurse) {
  var prev = this.model.getGeometry(cell);

  if (
    prev != null &&
    (prev.x != bounds.x || prev.y != bounds.y || prev.width != bounds.width || prev.height != bounds.height)
  ) {
    var geo = prev.clone();

    if (!ignoreRelative && geo.relative) {
      var offset = geo.offset;

      if (offset != null) {
        offset.x += bounds.x - geo.x;
        offset.y += bounds.y - geo.y;
      }
    } else {
      geo.x = bounds.x;
      geo.y = bounds.y;
    }

    geo.width = bounds.width;
    geo.height = bounds.height;

    if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates()) {
      geo.x = Math.max(0, geo.x);
      geo.y = Math.max(0, geo.y);
    }

    this.model.beginUpdate();
    try {
      if (recurse) {
        this.resizeChildCells(cell, geo);
      }

      this.model.setGeometry(cell, geo);
      this.constrainChildCells(cell);
    } finally {
      this.model.endUpdate();
    }
  }

  return prev;
};

/**
 * Function: resizeChildCells
 *
 * Resizes the child cells of the given cell for the given new geometry with
 * respect to the current geometry of the cell.
 *
 * Parameters:
 *
 * cell - <mxCell> that has been resized.
 * newGeo - <mxGeometry> that represents the new bounds.
 */
mxGraph.prototype.resizeChildCells = function (cell, newGeo) {
  var geo = this.model.getGeometry(cell);
  var dx = geo.width != 0 ? newGeo.width / geo.width : 1;
  var dy = geo.height != 0 ? newGeo.height / geo.height : 1;
  var childCount = this.model.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    this.scaleCell(this.model.getChildAt(cell, i), dx, dy, true);
  }
};

/**
 * Function: constrainChildCells
 *
 * Constrains the children of the given cell using <constrainChild>.
 *
 * Parameters:
 *
 * cell - <mxCell> that has been resized.
 */
mxGraph.prototype.constrainChildCells = function (cell) {
  var childCount = this.model.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    this.constrainChild(this.model.getChildAt(cell, i));
  }
};

/**
 * Function: scaleCell
 *
 * Scales the points, position and size of the given cell according to the
 * given vertical and horizontal scaling factors.
 *
 * Parameters:
 *
 * cell - <mxCell> whose geometry should be scaled.
 * dx - Horizontal scaling factor.
 * dy - Vertical scaling factor.
 * recurse - Boolean indicating if the child cells should be scaled.
 */
mxGraph.prototype.scaleCell = function (cell, dx, dy, recurse) {
  var geo = this.model.getGeometry(cell);

  if (geo != null) {
    var style = this.getCurrentCellStyle(cell);
    geo = geo.clone();

    // Stores values for restoring based on style
    var x = geo.x;
    var y = geo.y;
    var w = geo.width;
    var h = geo.height;

    geo.scale(dx, dy, style[mxConstants.STYLE_ASPECT] == 'fixed');

    if (style[mxConstants.STYLE_RESIZE_WIDTH] == '1') {
      geo.width = w * dx;
    } else if (style[mxConstants.STYLE_RESIZE_WIDTH] == '0') {
      geo.width = w;
    }

    if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '1') {
      geo.height = h * dy;
    } else if (style[mxConstants.STYLE_RESIZE_HEIGHT] == '0') {
      geo.height = h;
    }

    if (!this.isCellMovable(cell)) {
      geo.x = x;
      geo.y = y;
    }

    if (!this.isCellResizable(cell)) {
      geo.width = w;
      geo.height = h;
    }

    if (this.model.isVertex(cell)) {
      this.cellResized(cell, geo, true, recurse);
    } else {
      this.model.setGeometry(cell, geo);
    }
  }
};

/**
 * Function: extendParent
 *
 * Resizes the parents recursively so that they contain the complete area
 * of the resized child cell.
 *
 * Parameters:
 *
 * cell - <mxCell> that has been resized.
 */
mxGraph.prototype.extendParent = function (cell) {
  if (cell != null) {
    var parent = this.model.getParent(cell);
    var p = this.getCellGeometry(parent);

    if (parent != null && p != null && !this.isCellCollapsed(parent)) {
      var geo = this.getCellGeometry(cell);

      if (geo != null && !geo.relative && (p.width < geo.x + geo.width || p.height < geo.y + geo.height)) {
        p = p.clone();

        p.width = Math.max(p.width, geo.x + geo.width);
        p.height = Math.max(p.height, geo.y + geo.height);

        this.cellsResized([parent], [p], false);
      }
    }
  }
};

/**
 * Group: Cell moving
 */

/**
 * Function: importCells
 *
 * Clones and inserts the given cells into the graph using the move
 * method and returns the inserted cells. This shortcut is used if
 * cells are inserted via datatransfer.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be imported.
 * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
 * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
 * target - <mxCell> that represents the new parent of the cells.
 * evt - Mouseevent that triggered the invocation.
 * mapping - Optional mapping for existing clones.
 */
mxGraph.prototype.importCells = function (cells, dx, dy, target, evt, mapping) {
  return this.moveCells(cells, dx, dy, true, target, evt, mapping);
};

/**
 * Function: moveCells
 *
 * Moves or clones the specified cells and moves the cells or clones by the
 * given amount, adding them to the optional target cell. The evt is the
 * mouse event as the mouse was released. The change is carried out using
 * <cellsMoved>. This method fires <mxEvent.MOVE_CELLS> while the
 * transaction is in progress. Returns the cells that were moved.
 *
 * Use the following code to move all cells in the graph.
 *
 * (code)
 * graph.moveCells(graph.getChildCells(null, true, true), 10, 10);
 * (end)
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be moved, cloned or added to the target.
 * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
 * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
 * clone - Boolean indicating if the cells should be cloned. Default is false.
 * target - <mxCell> that represents the new parent of the cells.
 * evt - Mouseevent that triggered the invocation.
 * mapping - Optional mapping for existing clones.
 */
mxGraph.prototype.moveCells = function (cells, dx, dy, clone, target, evt, mapping) {
  dx = dx != null ? dx : 0;
  dy = dy != null ? dy : 0;
  clone = clone != null ? clone : false;

  if (cells != null && (dx != 0 || dy != 0 || clone || target != null)) {
    // Removes descendants with ancestors in cells to avoid multiple moving
    cells = this.model.getTopmostCells(cells);
    var origCells = cells;

    this.model.beginUpdate();
    try {
      // Faster cell lookups to remove relative edge labels with selected
      // terminals to avoid explicit and implicit move at same time
      var dict = new mxDictionary();

      for (var i = 0; i < cells.length; i++) {
        dict.put(cells[i], true);
      }

      var isSelected = mxUtils.bind(this, function (cell) {
        while (cell != null) {
          if (dict.get(cell)) {
            return true;
          }

          cell = this.model.getParent(cell);
        }

        return false;
      });

      // Removes relative edge labels with selected terminals
      var checked = [];

      for (var i = 0; i < cells.length; i++) {
        var geo = this.getCellGeometry(cells[i]);
        var parent = this.model.getParent(cells[i]);

        if (
          geo == null ||
          !geo.relative ||
          !this.model.isEdge(parent) ||
          (!isSelected(this.model.getTerminal(parent, true)) && !isSelected(this.model.getTerminal(parent, false)))
        ) {
          checked.push(cells[i]);
        }
      }

      cells = checked;

      if (clone) {
        cells = this.cloneCells(cells, this.isCloneInvalidEdges(), mapping);

        if (target == null) {
          target = this.getDefaultParent();
        }
      }

      // FIXME: Cells should always be inserted first before any other edit
      // to avoid forward references in sessions.
      // Need to disable allowNegativeCoordinates if target not null to
      // allow for temporary negative numbers until cellsAdded is called.
      var previous = this.isAllowNegativeCoordinates();

      if (target != null) {
        this.setAllowNegativeCoordinates(true);
      }

      this.cellsMoved(
        cells,
        dx,
        dy,
        !clone && this.isDisconnectOnMove() && this.isAllowDanglingEdges(),
        target == null,
        this.isExtendParentsOnMove() && target == null
      );

      this.setAllowNegativeCoordinates(previous);

      if (target != null) {
        var index = this.model.getChildCount(target);
        this.cellsAdded(cells, target, index, null, null, true);

        // Restores parent edge on cloned edge labels
        if (clone) {
          for (var i = 0; i < cells.length; i++) {
            var geo = this.getCellGeometry(cells[i]);
            var parent = this.model.getParent(origCells[i]);

            if (geo != null && geo.relative && this.model.isEdge(parent) && this.model.contains(parent)) {
              this.model.add(parent, cells[i]);
            }
          }
        }
      }

      // Dispatches a move event
      this.fireEvent(
        new mxEventObject(
          mxEvent.MOVE_CELLS,
          'cells',
          cells,
          'dx',
          dx,
          'dy',
          dy,
          'clone',
          clone,
          'target',
          target,
          'event',
          evt
        )
      );
    } finally {
      this.model.endUpdate();
    }
  }

  return cells;
};

/**
 * Function: cellsMoved
 *
 * Moves the specified cells by the given vector, disconnecting the cells
 * using disconnectGraph is disconnect is true. This method fires
 * <mxEvent.CELLS_MOVED> while the transaction is in progress.
 */
mxGraph.prototype.cellsMoved = function (cells, dx, dy, disconnect, constrain, extend) {
  if (cells != null && (dx != 0 || dy != 0)) {
    extend = extend != null ? extend : false;

    this.model.beginUpdate();
    try {
      if (disconnect) {
        this.disconnectGraph(cells);
      }

      for (var i = 0; i < cells.length; i++) {
        this.translateCell(cells[i], dx, dy);

        if (extend && this.isExtendParent(cells[i])) {
          this.extendParent(cells[i]);
        } else if (constrain) {
          this.constrainChild(cells[i]);
        }
      }

      if (this.resetEdgesOnMove) {
        this.resetEdges(cells);
      }

      this.fireEvent(
        new mxEventObject(mxEvent.CELLS_MOVED, 'cells', cells, 'dx', dx, 'dy', dy, 'disconnect', disconnect)
      );
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Function: translateCell
 *
 * Translates the geometry of the given cell and stores the new,
 * translated geometry in the model as an atomic change.
 */
mxGraph.prototype.translateCell = function (cell, dx, dy) {
  var geo = this.model.getGeometry(cell);

  if (geo != null) {
    dx = parseFloat(dx);
    dy = parseFloat(dy);
    geo = geo.clone();
    geo.translate(dx, dy);

    if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates()) {
      geo.x = Math.max(0, parseFloat(geo.x));
      geo.y = Math.max(0, parseFloat(geo.y));
    }

    if (geo.relative && !this.model.isEdge(cell)) {
      var parent = this.model.getParent(cell);
      var angle = 0;

      if (this.model.isVertex(parent)) {
        var style = this.getCurrentCellStyle(parent);
        angle = mxUtils.getValue(style, mxConstants.STYLE_ROTATION, 0);
      }

      if (angle != 0) {
        var rad = mxUtils.toRadians(-angle);
        var cos = Math.cos(rad);
        var sin = Math.sin(rad);
        var pt = mxUtils.getRotatedPoint(new mxPoint(dx, dy), cos, sin, new mxPoint(0, 0));
        dx = pt.x;
        dy = pt.y;
      }

      if (geo.offset == null) {
        geo.offset = new mxPoint(dx, dy);
      } else {
        geo.offset.x = parseFloat(geo.offset.x) + dx;
        geo.offset.y = parseFloat(geo.offset.y) + dy;
      }
    }

    this.model.setGeometry(cell, geo);
  }
};

/**
 * Function: getCellContainmentArea
 *
 * Returns the <mxRectangle> inside which a cell is to be kept.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the area should be returned.
 */
mxGraph.prototype.getCellContainmentArea = function (cell) {
  if (cell != null && !this.model.isEdge(cell)) {
    var parent = this.model.getParent(cell);

    if (parent != null && parent != this.getDefaultParent()) {
      var g = this.model.getGeometry(parent);

      if (g != null) {
        var x = 0;
        var y = 0;
        var w = g.width;
        var h = g.height;

        if (this.isSwimlane(parent)) {
          var size = this.getStartSize(parent);
          var style = this.getCurrentCellStyle(parent);
          var dir = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
          var flipH = mxUtils.getValue(style, mxConstants.STYLE_FLIPH, 0) == 1;
          var flipV = mxUtils.getValue(style, mxConstants.STYLE_FLIPV, 0) == 1;

          if (dir == mxConstants.DIRECTION_SOUTH || dir == mxConstants.DIRECTION_NORTH) {
            var tmp = size.width;
            size.width = size.height;
            size.height = tmp;
          }

          if (
            (dir == mxConstants.DIRECTION_EAST && !flipV) ||
            (dir == mxConstants.DIRECTION_NORTH && !flipH) ||
            (dir == mxConstants.DIRECTION_WEST && flipV) ||
            (dir == mxConstants.DIRECTION_SOUTH && flipH)
          ) {
            x = size.width;
            y = size.height;
          }

          w -= size.width;
          h -= size.height;
        }

        return new mxRectangle(x, y, w, h);
      }
    }
  }

  return null;
};

/**
 * Function: getMaximumGraphBounds
 *
 * Returns the bounds inside which the diagram should be kept as an
 * <mxRectangle>.
 */
mxGraph.prototype.getMaximumGraphBounds = function () {
  return this.maximumGraphBounds;
};

/**
 * Function: constrainChild
 *
 * Keeps the given cell inside the bounds returned by
 * <getCellContainmentArea> for its parent, according to the rules defined by
 * <getOverlap> and <isConstrainChild>. This modifies the cell's geometry
 * in-place and does not clone it.
 *
 * Parameters:
 *
 * cells - <mxCell> which should be constrained.
 * sizeFirst - Specifies if the size should be changed first. Default is true.
 */
mxGraph.prototype.constrainChild = function (cell, sizeFirst) {
  sizeFirst = sizeFirst != null ? sizeFirst : true;

  if (cell != null) {
    var geo = this.getCellGeometry(cell);

    if (geo != null && (this.isConstrainRelativeChildren() || !geo.relative)) {
      var parent = this.model.getParent(cell);
      var pgeo = this.getCellGeometry(parent);
      var max = this.getMaximumGraphBounds();

      // Finds parent offset
      if (max != null) {
        var off = this.getBoundingBoxFromGeometry([parent], false);

        if (off != null) {
          max = mxRectangle.fromRectangle(max);

          max.x -= off.x;
          max.y -= off.y;
        }
      }

      if (this.isConstrainChild(cell)) {
        var tmp = this.getCellContainmentArea(cell);

        if (tmp != null) {
          var overlap = this.getOverlap(cell);

          if (overlap > 0) {
            tmp = mxRectangle.fromRectangle(tmp);

            tmp.x -= tmp.width * overlap;
            tmp.y -= tmp.height * overlap;
            tmp.width += 2 * tmp.width * overlap;
            tmp.height += 2 * tmp.height * overlap;
          }

          // Find the intersection between max and tmp
          if (max == null) {
            max = tmp;
          } else {
            max = mxRectangle.fromRectangle(max);
            max.intersect(tmp);
          }
        }
      }

      if (max != null) {
        var cells = [cell];

        if (!this.isCellCollapsed(cell)) {
          var desc = this.model.getDescendants(cell);

          for (var i = 0; i < desc.length; i++) {
            if (this.isCellVisible(desc[i])) {
              cells.push(desc[i]);
            }
          }
        }

        var bbox = this.getBoundingBoxFromGeometry(cells, false);

        if (bbox != null) {
          geo = geo.clone();

          // Cumulative horizontal movement
          var dx = 0;

          if (geo.width > max.width) {
            dx = geo.width - max.width;
            geo.width -= dx;
          }

          if (bbox.x + bbox.width > max.x + max.width) {
            dx -= bbox.x + bbox.width - max.x - max.width - dx;
          }

          // Cumulative vertical movement
          var dy = 0;

          if (geo.height > max.height) {
            dy = geo.height - max.height;
            geo.height -= dy;
          }

          if (bbox.y + bbox.height > max.y + max.height) {
            dy -= bbox.y + bbox.height - max.y - max.height - dy;
          }

          if (bbox.x < max.x) {
            dx -= bbox.x - max.x;
          }

          if (bbox.y < max.y) {
            dy -= bbox.y - max.y;
          }

          if (dx != 0 || dy != 0) {
            if (geo.relative) {
              // Relative geometries are moved via absolute offset
              if (geo.offset == null) {
                geo.offset = new mxPoint();
              }

              geo.offset.x += dx;
              geo.offset.y += dy;
            } else {
              geo.x += dx;
              geo.y += dy;
            }
          }

          this.model.setGeometry(cell, geo);
        }
      }
    }
  }
};

/**
 * Function: resetEdges
 *
 * Resets the control points of the edges that are connected to the given
 * cells if not both ends of the edge are in the given cells array.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> for which the connected edges should be
 * reset.
 */
mxGraph.prototype.resetEdges = function (cells) {
  if (cells != null) {
    // Prepares faster cells lookup
    var dict = new mxDictionary();

    for (var i = 0; i < cells.length; i++) {
      dict.put(cells[i], true);
    }

    this.model.beginUpdate();
    try {
      for (var i = 0; i < cells.length; i++) {
        var edges = this.model.getEdges(cells[i]);

        if (edges != null) {
          for (var j = 0; j < edges.length; j++) {
            var state = this.view.getState(edges[j]);

            var source = state != null ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[j], true);
            var target =
              state != null ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[j], false);

            // Checks if one of the terminals is not in the given array
            if (!dict.get(source) || !dict.get(target)) {
              this.resetEdge(edges[j]);
            }
          }
        }

        this.resetEdges(this.model.getChildren(cells[i]));
      }
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Function: resetEdge
 *
 * Resets the control points of the given edge.
 *
 * Parameters:
 *
 * edge - <mxCell> whose points should be reset.
 */
mxGraph.prototype.resetEdge = function (edge) {
  var geo = this.model.getGeometry(edge);

  // Resets the control points
  if (geo != null && geo.points != null && geo.points.length > 0) {
    geo = geo.clone();
    geo.points = [];
    this.model.setGeometry(edge, geo);
  }

  return edge;
};

/**
 * Group: Cell connecting and connection constraints
 */

/**
 * Function: getOutlineConstraint
 *
 * Returns the constraint used to connect to the outline of the given state.
 */
mxGraph.prototype.getOutlineConstraint = function (point, terminalState, me) {
  if (terminalState.shape != null) {
    var bounds = this.view.getPerimeterBounds(terminalState);
    var direction = terminalState.style[mxConstants.STYLE_DIRECTION];

    if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH) {
      bounds.x += bounds.width / 2 - bounds.height / 2;
      bounds.y += bounds.height / 2 - bounds.width / 2;
      var tmp = bounds.width;
      bounds.width = bounds.height;
      bounds.height = tmp;
    }

    var alpha = mxUtils.toRadians(terminalState.shape.getShapeRotation());

    if (alpha != 0) {
      var cos = Math.cos(-alpha);
      var sin = Math.sin(-alpha);

      var ct = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
      point = mxUtils.getRotatedPoint(point, cos, sin, ct);
    }

    var sx = 1;
    var sy = 1;
    var dx = 0;
    var dy = 0;

    // LATER: Add flipping support for image shapes
    if (this.getModel().isVertex(terminalState.cell)) {
      var flipH = terminalState.style[mxConstants.STYLE_FLIPH];
      var flipV = terminalState.style[mxConstants.STYLE_FLIPV];

      // Legacy support for stencilFlipH/V
      if (terminalState.shape != null && terminalState.shape.stencil != null) {
        flipH = mxUtils.getValue(terminalState.style, 'stencilFlipH', 0) == 1 || flipH;
        flipV = mxUtils.getValue(terminalState.style, 'stencilFlipV', 0) == 1 || flipV;
      }

      if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH) {
        var tmp = flipH;
        flipH = flipV;
        flipV = tmp;
      }

      if (flipH) {
        sx = -1;
        dx = -bounds.width;
      }

      if (flipV) {
        sy = -1;
        dy = -bounds.height;
      }
    }

    point = new mxPoint((point.x - bounds.x) * sx - dx + bounds.x, (point.y - bounds.y) * sy - dy + bounds.y);

    var x = bounds.width == 0 ? 0 : Math.round(((point.x - bounds.x) * 1000) / bounds.width) / 1000;
    var y = bounds.height == 0 ? 0 : Math.round(((point.y - bounds.y) * 1000) / bounds.height) / 1000;

    return new mxConnectionConstraint(new mxPoint(x, y), false);
  }

  return null;
};

/**
 * Function: getAllConnectionConstraints
 *
 * Returns an array of all <mxConnectionConstraints> for the given terminal. If
 * the shape of the given terminal is a <mxStencilShape> then the constraints
 * of the corresponding <mxStencil> are returned.
 *
 * Parameters:
 *
 * terminal - <mxCellState> that represents the terminal.
 * source - Boolean that specifies if the terminal is the source or target.
 */
mxGraph.prototype.getAllConnectionConstraints = function (terminal, source) {
  if (terminal != null && terminal.shape != null && terminal.shape.stencil != null) {
    return terminal.shape.stencil.constraints;
  }

  return null;
};

/**
 * Function: getConnectionConstraint
 *
 * Returns an <mxConnectionConstraint> that describes the given connection
 * point. This result can then be passed to <getConnectionPoint>.
 *
 * Parameters:
 *
 * edge - <mxCellState> that represents the edge.
 * terminal - <mxCellState> that represents the terminal.
 * source - Boolean indicating if the terminal is the source or target.
 */
mxGraph.prototype.getConnectionConstraint = function (edge, terminal, source) {
  var point = null;
  var x = edge.style[source ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];

  if (x != null) {
    var y = edge.style[source ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];

    if (y != null) {
      point = new mxPoint(parseFloat(x), parseFloat(y));
    }
  }

  var perimeter = false;
  var dx = 0,
    dy = 0;

  if (point != null) {
    perimeter = mxUtils.getValue(
      edge.style,
      source ? mxConstants.STYLE_EXIT_PERIMETER : mxConstants.STYLE_ENTRY_PERIMETER,
      true
    );

    //Add entry/exit offset
    dx = parseFloat(edge.style[source ? mxConstants.STYLE_EXIT_DX : mxConstants.STYLE_ENTRY_DX]);
    dy = parseFloat(edge.style[source ? mxConstants.STYLE_EXIT_DY : mxConstants.STYLE_ENTRY_DY]);

    dx = isFinite(dx) ? dx : 0;
    dy = isFinite(dy) ? dy : 0;
  }

  return new mxConnectionConstraint(point, perimeter, null, dx, dy);
};

/**
 * Function: setConnectionConstraint
 *
 * Sets the <mxConnectionConstraint> that describes the given connection point.
 * If no constraint is given then nothing is changed. To remove an existing
 * constraint from the given edge, use an empty constraint instead.
 *
 * Parameters:
 *
 * edge - <mxCell> that represents the edge.
 * terminal - <mxCell> that represents the terminal.
 * source - Boolean indicating if the terminal is the source or target.
 * constraint - Optional <mxConnectionConstraint> to be used for this
 * connection.
 */
mxGraph.prototype.setConnectionConstraint = function (edge, terminal, source, constraint) {
  if (constraint != null) {
    this.model.beginUpdate();

    try {
      if (constraint == null || constraint.point == null) {
        this.setCellStyles(source ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X, null, [edge]);
        this.setCellStyles(source ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y, null, [edge]);
        this.setCellStyles(source ? mxConstants.STYLE_EXIT_DX : mxConstants.STYLE_ENTRY_DX, null, [edge]);
        this.setCellStyles(source ? mxConstants.STYLE_EXIT_DY : mxConstants.STYLE_ENTRY_DY, null, [edge]);
        this.setCellStyles(source ? mxConstants.STYLE_EXIT_PERIMETER : mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
      } else if (constraint.point != null) {
        this.setCellStyles(source ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X, constraint.point.x, [edge]);
        this.setCellStyles(source ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y, constraint.point.y, [edge]);
        this.setCellStyles(source ? mxConstants.STYLE_EXIT_DX : mxConstants.STYLE_ENTRY_DX, constraint.dx, [edge]);
        this.setCellStyles(source ? mxConstants.STYLE_EXIT_DY : mxConstants.STYLE_ENTRY_DY, constraint.dy, [edge]);

        // Only writes 0 since 1 is default
        if (!constraint.perimeter) {
          this.setCellStyles(source ? mxConstants.STYLE_EXIT_PERIMETER : mxConstants.STYLE_ENTRY_PERIMETER, '0', [
            edge,
          ]);
        } else {
          this.setCellStyles(source ? mxConstants.STYLE_EXIT_PERIMETER : mxConstants.STYLE_ENTRY_PERIMETER, null, [
            edge,
          ]);
        }
      }
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Function: getConnectionPoint
 *
 * Returns the nearest point in the list of absolute points or the center
 * of the opposite terminal.
 *
 * Parameters:
 *
 * vertex - <mxCellState> that represents the vertex.
 * constraint - <mxConnectionConstraint> that represents the connection point
 * constraint as returned by <getConnectionConstraint>.
 */
mxGraph.prototype.getConnectionPoint = function (vertex, constraint, round) {
  round = round != null ? round : true;
  var point = null;

  if (vertex != null && constraint.point != null) {
    var bounds = this.view.getPerimeterBounds(vertex);
    var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
    var direction = vertex.style[mxConstants.STYLE_DIRECTION];
    var r1 = 0;

    // Bounds need to be rotated by 90 degrees for further computation
    if (direction != null && mxUtils.getValue(vertex.style, mxConstants.STYLE_ANCHOR_POINT_DIRECTION, 1) == 1) {
      if (direction == mxConstants.DIRECTION_NORTH) {
        r1 += 270;
      } else if (direction == mxConstants.DIRECTION_WEST) {
        r1 += 180;
      } else if (direction == mxConstants.DIRECTION_SOUTH) {
        r1 += 90;
      }

      // Bounds need to be rotated by 90 degrees for further computation
      if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH) {
        bounds.rotate90();
      }
    }

    var scale = this.view.scale;
    point = new mxPoint(
      bounds.x + constraint.point.x * bounds.width + constraint.dx * scale,
      bounds.y + constraint.point.y * bounds.height + constraint.dy * scale
    );

    // Rotation for direction before projection on perimeter
    var r2 = vertex.style[mxConstants.STYLE_ROTATION] || 0;

    if (constraint.perimeter) {
      if (r1 != 0) {
        // Only 90 degrees steps possible here so no trig needed
        var cos = 0;
        var sin = 0;

        if (r1 == 90) {
          sin = 1;
        } else if (r1 == 180) {
          cos = -1;
        } else if (r1 == 270) {
          sin = -1;
        }

        point = mxUtils.getRotatedPoint(point, cos, sin, cx);
      }

      point = this.view.getPerimeterPoint(vertex, point, false);
    } else {
      r2 += r1;

      if (this.getModel().isVertex(vertex.cell)) {
        var flipH = vertex.style[mxConstants.STYLE_FLIPH] == 1;
        var flipV = vertex.style[mxConstants.STYLE_FLIPV] == 1;

        // Legacy support for stencilFlipH/V
        if (vertex.shape != null && vertex.shape.stencil != null) {
          flipH = mxUtils.getValue(vertex.style, 'stencilFlipH', 0) == 1 || flipH;
          flipV = mxUtils.getValue(vertex.style, 'stencilFlipV', 0) == 1 || flipV;
        }

        if (direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH) {
          var temp = flipH;
          flipH = flipV;
          flipV = temp;
        }

        if (flipH) {
          point.x = 2 * bounds.getCenterX() - point.x;
        }

        if (flipV) {
          point.y = 2 * bounds.getCenterY() - point.y;
        }
      }
    }

    // Generic rotation after projection on perimeter
    if (r2 != 0 && point != null) {
      var rad = mxUtils.toRadians(r2);
      var cos = Math.cos(rad);
      var sin = Math.sin(rad);

      point = mxUtils.getRotatedPoint(point, cos, sin, cx);
    }
  }

  if (round && point != null) {
    point.x = Math.round(point.x);
    point.y = Math.round(point.y);
  }

  return point;
};

/**
 * Function: connectCell
 *
 * Connects the specified end of the given edge to the given terminal
 * using <cellConnected> and fires <mxEvent.CONNECT_CELL> while the
 * transaction is in progress. Returns the updated edge.
 *
 * Parameters:
 *
 * edge - <mxCell> whose terminal should be updated.
 * terminal - <mxCell> that represents the new terminal to be used.
 * source - Boolean indicating if the new terminal is the source or target.
 * constraint - Optional <mxConnectionConstraint> to be used for this
 * connection.
 */
mxGraph.prototype.connectCell = function (edge, terminal, source, constraint) {
  this.model.beginUpdate();
  try {
    var previous = this.model.getTerminal(edge, source);
    this.cellConnected(edge, terminal, source, constraint);
    this.fireEvent(
      new mxEventObject(
        mxEvent.CONNECT_CELL,
        'edge',
        edge,
        'terminal',
        terminal,
        'source',
        source,
        'previous',
        previous
      )
    );
  } finally {
    this.model.endUpdate();
  }

  return edge;
};

/**
 * Function: cellConnected
 *
 * Sets the new terminal for the given edge and resets the edge points if
 * <resetEdgesOnConnect> is true. This method fires
 * <mxEvent.CELL_CONNECTED> while the transaction is in progress.
 *
 * Parameters:
 *
 * edge - <mxCell> whose terminal should be updated.
 * terminal - <mxCell> that represents the new terminal to be used.
 * source - Boolean indicating if the new terminal is the source or target.
 * constraint - <mxConnectionConstraint> to be used for this connection.
 */
mxGraph.prototype.cellConnected = function (edge, terminal, source, constraint) {
  if (edge != null) {
    this.model.beginUpdate();
    try {
      var previous = this.model.getTerminal(edge, source);

      // Updates the constraint
      this.setConnectionConstraint(edge, terminal, source, constraint);

      // Checks if the new terminal is a port, uses the ID of the port in the
      // style and the parent of the port as the actual terminal of the edge.
      if (this.isPortsEnabled()) {
        var id = null;

        if (this.isPort(terminal)) {
          id = terminal.getId();
          terminal = this.getTerminalForPort(terminal, source);
        }

        // Sets or resets all previous information for connecting to a child port
        var key = source ? mxConstants.STYLE_SOURCE_PORT : mxConstants.STYLE_TARGET_PORT;
        this.setCellStyles(key, id, [edge]);
      }

      this.model.setTerminal(edge, terminal, source);

      if (this.resetEdgesOnConnect) {
        this.resetEdge(edge);
      }

      this.fireEvent(
        new mxEventObject(
          mxEvent.CELL_CONNECTED,
          'edge',
          edge,
          'terminal',
          terminal,
          'source',
          source,
          'previous',
          previous
        )
      );
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Function: disconnectGraph
 *
 * Disconnects the given edges from the terminals which are not in the
 * given array.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be disconnected.
 */
mxGraph.prototype.disconnectGraph = function (cells) {
  if (cells != null) {
    this.model.beginUpdate();
    try {
      var scale = this.view.scale;
      var tr = this.view.translate;

      // Fast lookup for finding cells in array
      var dict = new mxDictionary();

      for (var i = 0; i < cells.length; i++) {
        dict.put(cells[i], true);
      }

      for (var i = 0; i < cells.length; i++) {
        if (this.model.isEdge(cells[i])) {
          var geo = this.model.getGeometry(cells[i]);

          if (geo != null) {
            var state = this.view.getState(cells[i]);
            var pstate = this.view.getState(this.model.getParent(cells[i]));

            if (state != null && pstate != null) {
              geo = geo.clone();

              var dx = -pstate.origin.x;
              var dy = -pstate.origin.y;
              var pts = state.absolutePoints;

              var src = this.model.getTerminal(cells[i], true);

              if (src != null && this.isCellDisconnectable(cells[i], src, true)) {
                while (src != null && !dict.get(src)) {
                  src = this.model.getParent(src);
                }

                if (src == null) {
                  geo.setTerminalPoint(new mxPoint(pts[0].x / scale - tr.x + dx, pts[0].y / scale - tr.y + dy), true);
                  this.model.setTerminal(cells[i], null, true);
                }
              }

              var trg = this.model.getTerminal(cells[i], false);

              if (trg != null && this.isCellDisconnectable(cells[i], trg, false)) {
                while (trg != null && !dict.get(trg)) {
                  trg = this.model.getParent(trg);
                }

                if (trg == null) {
                  var n = pts.length - 1;
                  geo.setTerminalPoint(new mxPoint(pts[n].x / scale - tr.x + dx, pts[n].y / scale - tr.y + dy), false);
                  this.model.setTerminal(cells[i], null, false);
                }
              }

              this.model.setGeometry(cells[i], geo);
            }
          }
        }
      }
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Group: Drilldown
 */

/**
 * Function: getCurrentRoot
 *
 * Returns the current root of the displayed cell hierarchy. This is a
 * shortcut to <mxGraphView.currentRoot> in <view>.
 */
mxGraph.prototype.getCurrentRoot = function () {
  return this.view.currentRoot;
};

/**
 * Function: getTranslateForRoot
 *
 * Returns the translation to be used if the given cell is the root cell as
 * an <mxPoint>. This implementation returns null.
 *
 * Example:
 *
 * To keep the children at their absolute position while stepping into groups,
 * this function can be overridden as follows.
 *
 * (code)
 * var offset = new mxPoint(0, 0);
 *
 * while (cell != null)
 * {
 *   var geo = this.model.getGeometry(cell);
 *
 *   if (geo != null)
 *   {
 *     offset.x -= geo.x;
 *     offset.y -= geo.y;
 *   }
 *
 *   cell = this.model.getParent(cell);
 * }
 *
 * return offset;
 * (end)
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the root.
 */
mxGraph.prototype.getTranslateForRoot = function (cell) {
  return null;
};

/**
 * Function: isPort
 *
 * Returns true if the given cell is a "port", that is, when connecting to
 * it, the cell returned by getTerminalForPort should be used as the
 * terminal and the port should be referenced by the ID in either the
 * mxConstants.STYLE_SOURCE_PORT or the or the
 * mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable.
 * This implementation always returns false.
 *
 * A typical implementation is the following:
 *
 * (code)
 * graph.isPort = function(cell)
 * {
 *   var geo = this.getCellGeometry(cell);
 *
 *   return (geo != null) ? geo.relative : false;
 * };
 * (end)
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the port.
 */
mxGraph.prototype.isPort = function (cell) {
  return false;
};

/**
 * Function: getTerminalForPort
 *
 * Returns the terminal to be used for a given port. This implementation
 * always returns the parent cell.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the port.
 * source - If the cell is the source or target port.
 */
mxGraph.prototype.getTerminalForPort = function (cell, source) {
  return this.model.getParent(cell);
};

/**
 * Function: getChildOffsetForCell
 *
 * Returns the offset to be used for the cells inside the given cell. The
 * root and layer cells may be identified using <mxGraphModel.isRoot> and
 * <mxGraphModel.isLayer>. For all other current roots, the
 * <mxGraphView.currentRoot> field points to the respective cell, so that
 * the following holds: cell == this.view.currentRoot. This implementation
 * returns null.
 *
 * Parameters:
 *
 * cell - <mxCell> whose offset should be returned.
 */
mxGraph.prototype.getChildOffsetForCell = function (cell) {
  return null;
};

/**
 * Function: enterGroup
 *
 * Uses the given cell as the root of the displayed cell hierarchy. If no
 * cell is specified then the selection cell is used. The cell is only used
 * if <isValidRoot> returns true.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> to be used as the new root. Default is the
 * selection cell.
 */
mxGraph.prototype.enterGroup = function (cell) {
  cell = cell || this.getSelectionCell();

  if (cell != null && this.isValidRoot(cell)) {
    this.view.setCurrentRoot(cell);
    this.clearSelection();
  }
};

/**
 * Function: exitGroup
 *
 * Changes the current root to the next valid root in the displayed cell
 * hierarchy.
 */
mxGraph.prototype.exitGroup = function () {
  var root = this.model.getRoot();
  var current = this.getCurrentRoot();

  if (current != null) {
    var next = this.model.getParent(current);

    // Finds the next valid root in the hierarchy
    while (next != root && !this.isValidRoot(next) && this.model.getParent(next) != root) {
      next = this.model.getParent(next);
    }

    // Clears the current root if the new root is
    // the model's root or one of the layers.
    if (next == root || this.model.getParent(next) == root) {
      this.view.setCurrentRoot(null);
    } else {
      this.view.setCurrentRoot(next);
    }

    var state = this.view.getState(current);

    // Selects the previous root in the graph
    if (state != null) {
      this.setSelectionCell(current);
    }
  }
};

/**
 * Function: home
 *
 * Uses the root of the model as the root of the displayed cell hierarchy
 * and selects the previous root.
 */
mxGraph.prototype.home = function () {
  var current = this.getCurrentRoot();

  if (current != null) {
    this.view.setCurrentRoot(null);
    var state = this.view.getState(current);

    if (state != null) {
      this.setSelectionCell(current);
    }
  }
};

/**
 * Function: isValidRoot
 *
 * Returns true if the given cell is a valid root for the cell display
 * hierarchy. This implementation returns true for all non-null values.
 *
 * Parameters:
 *
 * cell - <mxCell> which should be checked as a possible root.
 */
mxGraph.prototype.isValidRoot = function (cell) {
  return cell != null;
};

/**
 * Group: Graph display
 */

/**
 * Function: getGraphBounds
 *
 * Returns the bounds of the visible graph. Shortcut to
 * <mxGraphView.getGraphBounds>. See also: <getBoundingBoxFromGeometry>.
 */
mxGraph.prototype.getGraphBounds = function () {
  return this.view.getGraphBounds();
};

/**
 * Function: getCellBounds
 *
 * Returns the scaled, translated bounds for the given cell. See
 * <mxGraphView.getBounds> for arrays.
 *
 * Parameters:
 *
 * cell - <mxCell> whose bounds should be returned.
 * includeEdge - Optional boolean that specifies if the bounds of
 * the connected edges should be included. Default is false.
 * includeDescendants - Optional boolean that specifies if the bounds
 * of all descendants should be included. Default is false.
 */
mxGraph.prototype.getCellBounds = function (cell, includeEdges, includeDescendants) {
  var cells = [cell];

  // Includes all connected edges
  if (includeEdges) {
    cells = cells.concat(this.model.getEdges(cell));
  }

  var result = this.view.getBounds(cells);

  // Recursively includes the bounds of the children
  if (includeDescendants) {
    var childCount = this.model.getChildCount(cell);

    for (var i = 0; i < childCount; i++) {
      var tmp = this.getCellBounds(this.model.getChildAt(cell, i), includeEdges, true);

      if (result != null) {
        result.add(tmp);
      } else {
        result = tmp;
      }
    }
  }

  return result;
};

/**
 * Function: getBoundingBoxFromGeometry
 *
 * Returns the bounding box for the geometries of the vertices in the
 * given array of cells. This can be used to find the graph bounds during
 * a layout operation (ie. before the last endUpdate) as follows:
 *
 * (code)
 * var cells = graph.getChildCells(graph.getDefaultParent(), true, true);
 * var bounds = graph.getBoundingBoxFromGeometry(cells, true);
 * (end)
 *
 * This can then be used to move cells to the origin:
 *
 * (code)
 * if (bounds.x < 0 || bounds.y < 0)
 * {
 *   graph.moveCells(cells, -Math.min(bounds.x, 0), -Math.min(bounds.y, 0))
 * }
 * (end)
 *
 * Or to translate the graph view:
 *
 * (code)
 * if (bounds.x < 0 || bounds.y < 0)
 * {
 *   graph.view.setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0));
 * }
 * (end)
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose bounds should be returned.
 * includeEdges - Specifies if edge bounds should be included by computing
 * the bounding box for all points in geometry. Default is false.
 */
mxGraph.prototype.getBoundingBoxFromGeometry = function (cells, includeEdges) {
  includeEdges = includeEdges != null ? includeEdges : false;
  var result = null;

  if (cells != null) {
    for (var i = 0; i < cells.length; i++) {
      if (includeEdges || this.model.isVertex(cells[i])) {
        // Computes the bounding box for the points in the geometry
        var geo = this.getCellGeometry(cells[i]);

        if (geo != null) {
          var bbox = null;

          if (this.model.isEdge(cells[i])) {
            var addPoint = function (pt) {
              if (pt != null) {
                if (tmp == null) {
                  tmp = new mxRectangle(pt.x, pt.y, 0, 0);
                } else {
                  tmp.add(new mxRectangle(pt.x, pt.y, 0, 0));
                }
              }
            };

            if (this.model.getTerminal(cells[i], true) == null) {
              addPoint(geo.getTerminalPoint(true));
            }

            if (this.model.getTerminal(cells[i], false) == null) {
              addPoint(geo.getTerminalPoint(false));
            }

            var pts = geo.points;

            if (pts != null && pts.length > 0) {
              var tmp = new mxRectangle(pts[0].x, pts[0].y, 0, 0);

              for (var j = 1; j < pts.length; j++) {
                addPoint(pts[j]);
              }
            }

            bbox = tmp;
          } else {
            var parent = this.model.getParent(cells[i]);

            if (geo.relative) {
              if (this.model.isVertex(parent) && parent != this.view.currentRoot) {
                var tmp = this.getBoundingBoxFromGeometry([parent], false);

                if (tmp != null) {
                  bbox = new mxRectangle(geo.x * tmp.width, geo.y * tmp.height, geo.width, geo.height);

                  if (mxUtils.indexOf(cells, parent) >= 0) {
                    bbox.x += tmp.x;
                    bbox.y += tmp.y;
                  }
                }
              }
            } else {
              bbox = mxRectangle.fromRectangle(geo);

              if (this.model.isVertex(parent) && mxUtils.indexOf(cells, parent) >= 0) {
                var tmp = this.getBoundingBoxFromGeometry([parent], false);

                if (tmp != null) {
                  bbox.x += tmp.x;
                  bbox.y += tmp.y;
                }
              }
            }

            if (bbox != null && geo.offset != null) {
              bbox.x += geo.offset.x;
              bbox.y += geo.offset.y;
            }

            var style = this.getCurrentCellStyle(cells[i]);

            if (bbox != null) {
              var angle = mxUtils.getValue(style, mxConstants.STYLE_ROTATION, 0);

              if (angle != 0) {
                bbox = mxUtils.getBoundingBox(bbox, angle);
              }
            }
          }

          if (bbox != null) {
            if (result == null) {
              result = mxRectangle.fromRectangle(bbox);
            } else {
              result.add(bbox);
            }
          }
        }
      }
    }
  }

  return result;
};

/**
 * Function: refresh
 *
 * Clears all cell states or the states for the hierarchy starting at the
 * given cell and validates the graph. This fires a refresh event as the
 * last step.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> for which the cell states should be cleared.
 */
mxGraph.prototype.refresh = function (cell) {
  this.view.clear(cell, cell == null);
  this.view.validate();
  this.sizeDidChange();
  this.fireEvent(new mxEventObject(mxEvent.REFRESH));
};

/**
 * Function: snap
 *
 * Snaps the given numeric value to the grid if <gridEnabled> is true.
 *
 * Parameters:
 *
 * value - Numeric value to be snapped to the grid.
 */
mxGraph.prototype.snap = function (value) {
  if (this.gridEnabled) {
    value = Math.round(value / this.gridSize) * this.gridSize;
  }

  return value;
};

/**
 * Function: snapDelta
 *
 * Snaps the given delta with the given scaled bounds.
 */
mxGraph.prototype.snapDelta = function (delta, bounds, ignoreGrid, ignoreHorizontal, ignoreVertical) {
  var t = this.view.translate;
  var s = this.view.scale;

  if (!ignoreGrid && this.gridEnabled) {
    var tol = this.gridSize * s * 0.5;

    if (!ignoreHorizontal) {
      var tx = bounds.x - (this.snap(bounds.x / s - t.x) + t.x) * s;

      if (Math.abs(delta.x - tx) < tol) {
        delta.x = 0;
      } else {
        delta.x = this.snap(delta.x / s) * s - tx;
      }
    }

    if (!ignoreVertical) {
      var ty = bounds.y - (this.snap(bounds.y / s - t.y) + t.y) * s;

      if (Math.abs(delta.y - ty) < tol) {
        delta.y = 0;
      } else {
        delta.y = this.snap(delta.y / s) * s - ty;
      }
    }
  } else {
    var tol = 0.5 * s;

    if (!ignoreHorizontal) {
      var tx = bounds.x - (Math.round(bounds.x / s - t.x) + t.x) * s;

      if (Math.abs(delta.x - tx) < tol) {
        delta.x = 0;
      } else {
        delta.x = Math.round(delta.x / s) * s - tx;
      }
    }

    if (!ignoreVertical) {
      var ty = bounds.y - (Math.round(bounds.y / s - t.y) + t.y) * s;

      if (Math.abs(delta.y - ty) < tol) {
        delta.y = 0;
      } else {
        delta.y = Math.round(delta.y / s) * s - ty;
      }
    }
  }

  return delta;
};

/**
 * Function: panGraph
 *
 * Shifts the graph display by the given amount. This is used to preview
 * panning operations, use <mxGraphView.setTranslate> to set a persistent
 * translation of the view. Fires <mxEvent.PAN>.
 *
 * Parameters:
 *
 * dx - Amount to shift the graph along the x-axis.
 * dy - Amount to shift the graph along the y-axis.
 */
mxGraph.prototype.panGraph = function (dx, dy) {
  if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container)) {
    this.container.scrollLeft = -dx;
    this.container.scrollTop = -dy;
  } else {
    var canvas = this.view.getCanvas();

    if (this.dialect == mxConstants.DIALECT_SVG) {
      // Puts everything inside the container in a DIV so that it
      // can be moved without changing the state of the container
      if (dx == 0 && dy == 0) {
        // Workaround for ignored removeAttribute on SVG element in IE9 standards
        if (mxClient.IS_IE) {
          canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
        } else {
          canvas.removeAttribute('transform');
        }

        if (this.shiftPreview1 != null) {
          var child = this.shiftPreview1.firstChild;

          while (child != null) {
            var next = child.nextSibling;
            this.container.appendChild(child);
            child = next;
          }

          if (this.shiftPreview1.parentNode != null) {
            this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);
          }

          this.shiftPreview1 = null;

          this.container.appendChild(canvas.parentNode);

          child = this.shiftPreview2.firstChild;

          while (child != null) {
            var next = child.nextSibling;
            this.container.appendChild(child);
            child = next;
          }

          if (this.shiftPreview2.parentNode != null) {
            this.shiftPreview2.parentNode.removeChild(this.shiftPreview2);
          }

          this.shiftPreview2 = null;
        }
      } else {
        canvas.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');

        if (this.shiftPreview1 == null) {
          // Needs two divs for stuff before and after the SVG element
          this.shiftPreview1 = document.createElement('div');
          this.shiftPreview1.style.position = 'absolute';
          this.shiftPreview1.style.overflow = 'visible';

          this.shiftPreview2 = document.createElement('div');
          this.shiftPreview2.style.position = 'absolute';
          this.shiftPreview2.style.overflow = 'visible';

          var current = this.shiftPreview1;
          var child = this.container.firstChild;

          while (child != null) {
            var next = child.nextSibling;

            // SVG element is moved via transform attribute
            if (child != canvas.parentNode) {
              current.appendChild(child);
            } else {
              current = this.shiftPreview2;
            }

            child = next;
          }

          // Inserts elements only if not empty
          if (this.shiftPreview1.firstChild != null) {
            this.container.insertBefore(this.shiftPreview1, canvas.parentNode);
          }

          if (this.shiftPreview2.firstChild != null) {
            this.container.appendChild(this.shiftPreview2);
          }
        }

        this.shiftPreview1.style.left = dx + 'px';
        this.shiftPreview1.style.top = dy + 'px';
        this.shiftPreview2.style.left = dx + 'px';
        this.shiftPreview2.style.top = dy + 'px';
      }
    } else {
      canvas.style.left = dx + 'px';
      canvas.style.top = dy + 'px';
    }

    this.panDx = dx;
    this.panDy = dy;

    this.fireEvent(new mxEventObject(mxEvent.PAN));
  }
};

/**
 * Function: zoomIn
 *
 * Zooms into the graph by <zoomFactor>.
 */
mxGraph.prototype.zoomIn = function () {
  this.zoom(this.zoomFactor);
};

/**
 * Function: zoomOut
 *
 * Zooms out of the graph by <zoomFactor>.
 */
mxGraph.prototype.zoomOut = function () {
  this.zoom(1 / this.zoomFactor);
};

/**
 * Function: zoomActual
 *
 * Resets the zoom and panning in the view.
 */
mxGraph.prototype.zoomActual = function () {
  if (this.view.scale == 1) {
    this.view.setTranslate(0, 0);
  } else {
    this.view.translate.x = 0;
    this.view.translate.y = 0;

    this.view.setScale(1);
  }
};

/**
 * Function: zoomTo
 *
 * Zooms the graph to the given scale with an optional boolean center
 * argument, which is passd to <zoom>.
 */
mxGraph.prototype.zoomTo = function (scale, center) {
  this.zoom(scale / this.view.scale, center);
};

/**
 * Function: center
 *
 * Centers the graph in the container.
 *
 * Parameters:
 *
 * horizontal - Optional boolean that specifies if the graph should be centered
 * horizontally. Default is true.
 * vertical - Optional boolean that specifies if the graph should be centered
 * vertically. Default is true.
 * cx - Optional float that specifies the horizontal center. Default is 0.5.
 * cy - Optional float that specifies the vertical center. Default is 0.5.
 */
mxGraph.prototype.center = function (horizontal, vertical, cx, cy) {
  horizontal = horizontal != null ? horizontal : true;
  vertical = vertical != null ? vertical : true;
  cx = cx != null ? cx : 0.5;
  cy = cy != null ? cy : 0.5;

  var hasScrollbars = mxUtils.hasScrollbars(this.container);
  var padding = 2 * this.getBorder();
  var cw = this.container.clientWidth - padding;
  var ch = this.container.clientHeight - padding;
  var bounds = this.getGraphBounds();

  var t = this.view.translate;
  var s = this.view.scale;

  var dx = horizontal ? cw - bounds.width : 0;
  var dy = vertical ? ch - bounds.height : 0;

  if (!hasScrollbars) {
    this.view.setTranslate(
      horizontal ? Math.floor(t.x - bounds.x / s + (dx * cx) / s) : t.x,
      vertical ? Math.floor(t.y - bounds.y / s + (dy * cy) / s) : t.y
    );
  } else {
    bounds.x -= t.x;
    bounds.y -= t.y;

    var sw = this.container.scrollWidth;
    var sh = this.container.scrollHeight;

    if (sw > cw) {
      dx = 0;
    }

    if (sh > ch) {
      dy = 0;
    }

    this.view.setTranslate(Math.floor(dx / 2 - bounds.x), Math.floor(dy / 2 - bounds.y));
    this.container.scrollLeft = (sw - cw) / 2;
    this.container.scrollTop = (sh - ch) / 2;
  }
};

/**
 * Function: zoom
 *
 * Zooms the graph using the given factor. Center is an optional boolean
 * argument that keeps the graph scrolled to the center. If the center argument
 * is omitted, then <centerZoom> will be used as its value.
 */
mxGraph.prototype.zoom = function (factor, center) {
  center = center != null ? center : this.centerZoom;
  var scale = Math.round(this.view.scale * factor * 100) / 100;
  var state = this.view.getState(this.getSelectionCell());
  factor = scale / this.view.scale;

  if (this.keepSelectionVisibleOnZoom && state != null) {
    var rect = new mxRectangle(state.x * factor, state.y * factor, state.width * factor, state.height * factor);

    // Refreshes the display only once if a scroll is carried out
    this.view.scale = scale;

    if (!this.scrollRectToVisible(rect)) {
      this.view.revalidate();

      // Forces an event to be fired but does not revalidate again
      this.view.setScale(scale);
    }
  } else {
    var hasScrollbars = mxUtils.hasScrollbars(this.container);

    if (center && !hasScrollbars) {
      var dx = this.container.offsetWidth;
      var dy = this.container.offsetHeight;

      if (factor > 1) {
        var f = (factor - 1) / (scale * 2);
        dx *= -f;
        dy *= -f;
      } else {
        var f = (1 / factor - 1) / (this.view.scale * 2);
        dx *= f;
        dy *= f;
      }

      this.view.scaleAndTranslate(scale, this.view.translate.x + dx, this.view.translate.y + dy);
    } else {
      // Allows for changes of translate and scrollbars during setscale
      var tx = this.view.translate.x;
      var ty = this.view.translate.y;
      var sl = this.container.scrollLeft;
      var st = this.container.scrollTop;

      this.view.setScale(scale);

      if (hasScrollbars) {
        var dx = 0;
        var dy = 0;

        if (center) {
          dx = (this.container.offsetWidth * (factor - 1)) / 2;
          dy = (this.container.offsetHeight * (factor - 1)) / 2;
        }

        this.container.scrollLeft = (this.view.translate.x - tx) * this.view.scale + Math.round(sl * factor + dx);
        this.container.scrollTop = (this.view.translate.y - ty) * this.view.scale + Math.round(st * factor + dy);
      }
    }
  }
};

/**
 * Function: zoomToRect
 *
 * Zooms the graph to the specified rectangle. If the rectangle does not have same aspect
 * ratio as the display container, it is increased in the smaller relative dimension only
 * until the aspect match. The original rectangle is centralised within this expanded one.
 *
 * Note that the input rectangular must be un-scaled and un-translated.
 *
 * Parameters:
 *
 * rect - The un-scaled and un-translated rectangluar region that should be just visible
 * after the operation
 */
mxGraph.prototype.zoomToRect = function (rect) {
  var scaleX = this.container.clientWidth / rect.width;
  var scaleY = this.container.clientHeight / rect.height;
  var aspectFactor = scaleX / scaleY;

  // Remove any overlap of the rect outside the client area
  rect.x = Math.max(0, rect.x);
  rect.y = Math.max(0, rect.y);
  var rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
  var rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
  rect.width = rectRight - rect.x;
  rect.height = rectBottom - rect.y;

  // The selection area has to be increased to the same aspect
  // ratio as the container, centred around the centre point of the
  // original rect passed in.
  if (aspectFactor < 1.0) {
    // Height needs increasing
    var newHeight = rect.height / aspectFactor;
    var deltaHeightBuffer = (newHeight - rect.height) / 2.0;
    rect.height = newHeight;

    // Assign up to half the buffer to the upper part of the rect, not crossing 0
    // put the rest on the bottom
    var upperBuffer = Math.min(rect.y, deltaHeightBuffer);
    rect.y = rect.y - upperBuffer;

    // Check if the bottom has extended too far
    rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
    rect.height = rectBottom - rect.y;
  } else {
    // Width needs increasing
    var newWidth = rect.width * aspectFactor;
    var deltaWidthBuffer = (newWidth - rect.width) / 2.0;
    rect.width = newWidth;

    // Assign up to half the buffer to the upper part of the rect, not crossing 0
    // put the rest on the bottom
    var leftBuffer = Math.min(rect.x, deltaWidthBuffer);
    rect.x = rect.x - leftBuffer;

    // Check if the right hand side has extended too far
    rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
    rect.width = rectRight - rect.x;
  }

  var scale = this.container.clientWidth / rect.width;
  var newScale = this.view.scale * scale;

  if (!mxUtils.hasScrollbars(this.container)) {
    this.view.scaleAndTranslate(
      newScale,
      this.view.translate.x - rect.x / this.view.scale,
      this.view.translate.y - rect.y / this.view.scale
    );
  } else {
    this.view.setScale(newScale);
    this.container.scrollLeft = Math.round(rect.x * scale);
    this.container.scrollTop = Math.round(rect.y * scale);
  }
};

/**
 * Function: scrollCellToVisible
 *
 * Pans the graph so that it shows the given cell. Optionally the cell may
 * be centered in the container.
 *
 * To center a given graph if the <container> has no scrollbars, use the following code.
 *
 * [code]
 * var bounds = graph.getGraphBounds();
 * graph.view.setTranslate(-bounds.x - (bounds.width - container.clientWidth) / 2,
 * 						   -bounds.y - (bounds.height - container.clientHeight) / 2);
 * [/code]
 *
 * Parameters:
 *
 * cell - <mxCell> to be made visible.
 * center - Optional boolean flag. Default is false.
 */
mxGraph.prototype.scrollCellToVisible = function (cell, center) {
  var x = -this.view.translate.x;
  var y = -this.view.translate.y;

  var state = this.view.getState(cell);

  if (state != null) {
    var bounds = new mxRectangle(x + state.x, y + state.y, state.width, state.height);

    if (center && this.container != null) {
      var w = this.container.clientWidth;
      var h = this.container.clientHeight;

      bounds.x = bounds.getCenterX() - w / 2;
      bounds.width = w;
      bounds.y = bounds.getCenterY() - h / 2;
      bounds.height = h;
    }

    var tr = new mxPoint(this.view.translate.x, this.view.translate.y);

    if (this.scrollRectToVisible(bounds)) {
      // Triggers an update via the view's event source
      var tr2 = new mxPoint(this.view.translate.x, this.view.translate.y);
      this.view.translate.x = tr.x;
      this.view.translate.y = tr.y;
      this.view.setTranslate(tr2.x, tr2.y);
    }
  }
};

/**
 * Function: scrollRectToVisible
 *
 * Pans the graph so that it shows the given rectangle.
 *
 * Parameters:
 *
 * rect - <mxRectangle> to be made visible.
 */
mxGraph.prototype.scrollRectToVisible = function (rect) {
  var isChanged = false;

  if (rect != null) {
    var w = this.container.offsetWidth;
    var h = this.container.offsetHeight;

    var widthLimit = Math.min(w, rect.width);
    var heightLimit = Math.min(h, rect.height);

    if (mxUtils.hasScrollbars(this.container)) {
      var c = this.container;
      rect.x += this.view.translate.x;
      rect.y += this.view.translate.y;
      var dx = c.scrollLeft - rect.x;
      var ddx = Math.max(dx - c.scrollLeft, 0);

      if (dx > 0) {
        c.scrollLeft -= dx + 2;
      } else {
        dx = rect.x + widthLimit - c.scrollLeft - c.clientWidth;

        if (dx > 0) {
          c.scrollLeft += dx + 2;
        }
      }

      var dy = c.scrollTop - rect.y;
      var ddy = Math.max(0, dy - c.scrollTop);

      if (dy > 0) {
        c.scrollTop -= dy + 2;
      } else {
        dy = rect.y + heightLimit - c.scrollTop - c.clientHeight;

        if (dy > 0) {
          c.scrollTop += dy + 2;
        }
      }

      if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0)) {
        this.view.setTranslate(ddx, ddy);
      }
    } else {
      var x = -this.view.translate.x;
      var y = -this.view.translate.y;

      var s = this.view.scale;

      if (rect.x + widthLimit > x + w) {
        this.view.translate.x -= (rect.x + widthLimit - w - x) / s;
        isChanged = true;
      }

      if (rect.y + heightLimit > y + h) {
        this.view.translate.y -= (rect.y + heightLimit - h - y) / s;
        isChanged = true;
      }

      if (rect.x < x) {
        this.view.translate.x += (x - rect.x) / s;
        isChanged = true;
      }

      if (rect.y < y) {
        this.view.translate.y += (y - rect.y) / s;
        isChanged = true;
      }

      if (isChanged) {
        this.view.refresh();

        // Repaints selection marker (ticket 18)
        if (this.selectionCellsHandler != null) {
          this.selectionCellsHandler.refresh();
        }
      }
    }
  }

  return isChanged;
};

/**
 * Function: getCellGeometry
 *
 * Returns the <mxGeometry> for the given cell. This implementation uses
 * <mxGraphModel.getGeometry>. Subclasses can override this to implement
 * specific geometries for cells in only one graph, that is, it can return
 * geometries that depend on the current state of the view.
 *
 * Parameters:
 *
 * cell - <mxCell> whose geometry should be returned.
 */
mxGraph.prototype.getCellGeometry = function (cell) {
  return this.model.getGeometry(cell);
};

/**
 * Function: isCellVisible
 *
 * Returns true if the given cell is visible in this graph. This
 * implementation uses <mxGraphModel.isVisible>. Subclassers can override
 * this to implement specific visibility for cells in only one graph, that
 * is, without affecting the visible state of the cell.
 *
 * When using dynamic filter expressions for cell visibility, then the
 * graph should be revalidated after the filter expression has changed.
 *
 * Parameters:
 *
 * cell - <mxCell> whose visible state should be returned.
 */
mxGraph.prototype.isCellVisible = function (cell) {
  return this.model.isVisible(cell);
};

/**
 * Function: isCellCollapsed
 *
 * Returns true if the given cell is collapsed in this graph. This
 * implementation uses <mxGraphModel.isCollapsed>. Subclassers can override
 * this to implement specific collapsed states for cells in only one graph,
 * that is, without affecting the collapsed state of the cell.
 *
 * When using dynamic filter expressions for the collapsed state, then the
 * graph should be revalidated after the filter expression has changed.
 *
 * Parameters:
 *
 * cell - <mxCell> whose collapsed state should be returned.
 */
mxGraph.prototype.isCellCollapsed = function (cell) {
  return this.model.isCollapsed(cell);
};

/**
 * Function: isCellConnectable
 *
 * Returns true if the given cell is connectable in this graph. This
 * implementation uses <mxGraphModel.isConnectable>. Subclassers can override
 * this to implement specific connectable states for cells in only one graph,
 * that is, without affecting the connectable state of the cell in the model.
 *
 * Parameters:
 *
 * cell - <mxCell> whose connectable state should be returned.
 */
mxGraph.prototype.isCellConnectable = function (cell) {
  return this.model.isConnectable(cell);
};

/**
 * Function: isOrthogonal
 *
 * Returns true if perimeter points should be computed such that the
 * resulting edge has only horizontal or vertical segments.
 *
 * Parameters:
 *
 * edge - <mxCellState> that represents the edge.
 */
mxGraph.prototype.isOrthogonal = function (edge) {
  var orthogonal = edge.style[mxConstants.STYLE_ORTHOGONAL];

  if (orthogonal != null) {
    return orthogonal;
  }

  var tmp = this.view.getEdgeStyle(edge);

  return (
    tmp == mxEdgeStyle.SegmentConnector ||
    tmp == mxEdgeStyle.ElbowConnector ||
    tmp == mxEdgeStyle.SideToSide ||
    tmp == mxEdgeStyle.TopToBottom ||
    tmp == mxEdgeStyle.EntityRelation ||
    tmp == mxEdgeStyle.OrthConnector
  );
};

/**
 * Function: isLoop
 *
 * Returns true if the given cell state is a loop.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents a potential loop.
 */
mxGraph.prototype.isLoop = function (state) {
  var src = state.getVisibleTerminalState(true);
  var trg = state.getVisibleTerminalState(false);

  return src != null && src == trg;
};

/**
 * Function: isCloneEvent
 *
 * Returns true if the given event is a clone event. This implementation
 * returns true if control is pressed.
 */
mxGraph.prototype.isCloneEvent = function (evt) {
  return mxEvent.isControlDown(evt);
};

/**
 * Function: isTransparentClickEvent
 *
 * Hook for implementing click-through behaviour on selected cells. If this
 * returns true the cell behind the selected cell will be selected. This
 * implementation returns false;
 */
mxGraph.prototype.isTransparentClickEvent = function (evt) {
  return false;
};

/**
 * Function: isToggleEvent
 *
 * Returns true if the given event is a toggle event. This implementation
 * returns true if the meta key (Cmd) is pressed on Macs or if control is
 * pressed on any other platform.
 */
mxGraph.prototype.isToggleEvent = function (evt) {
  return mxClient.IS_MAC ? mxEvent.isMetaDown(evt) : mxEvent.isControlDown(evt);
};

/**
 * Function: isGridEnabledEvent
 *
 * Returns true if the given mouse event should be aligned to the grid.
 */
mxGraph.prototype.isGridEnabledEvent = function (evt) {
  return evt != null && !mxEvent.isAltDown(evt);
};

/**
 * Function: isConstrainedEvent
 *
 * Returns true if the given mouse event should be aligned to the grid.
 */
mxGraph.prototype.isConstrainedEvent = function (evt) {
  return mxEvent.isShiftDown(evt);
};

/**
 * Function: isIgnoreTerminalEvent
 *
 * Returns true if the given mouse event should not allow any connections to be
 * made. This implementation returns false.
 */
mxGraph.prototype.isIgnoreTerminalEvent = function (evt) {
  return false;
};

/**
 * Group: Validation
 */

/**
 * Function: validationAlert
 *
 * Displays the given validation error in a dialog. This implementation uses
 * mxUtils.alert.
 */
mxGraph.prototype.validationAlert = function (message) {
  mxUtils.alert(message);
};

/**
 * Function: isEdgeValid
 *
 * Checks if the return value of <getEdgeValidationError> for the given
 * arguments is null.
 *
 * Parameters:
 *
 * edge - <mxCell> that represents the edge to validate.
 * source - <mxCell> that represents the source terminal.
 * target - <mxCell> that represents the target terminal.
 */
mxGraph.prototype.isEdgeValid = function (edge, source, target) {
  return this.getEdgeValidationError(edge, source, target) == null;
};

/**
 * Function: getEdgeValidationError
 *
 * Returns the validation error message to be displayed when inserting or
 * changing an edges' connectivity. A return value of null means the edge
 * is valid, a return value of '' means it's not valid, but do not display
 * an error message. Any other (non-empty) string returned from this method
 * is displayed as an error message when trying to connect an edge to a
 * source and target. This implementation uses the <multiplicities>, and
 * checks <multigraph>, <allowDanglingEdges> and <allowLoops> to generate
 * validation errors.
 *
 * For extending this method with specific checks for source/target cells,
 * the method can be extended as follows. Returning an empty string means
 * the edge is invalid with no error message, a non-null string specifies
 * the error message, and null means the edge is valid.
 *
 * (code)
 * graph.getEdgeValidationError = function(edge, source, target)
 * {
 *   if (source != null && target != null &&
 *     this.model.getValue(source) != null &&
 *     this.model.getValue(target) != null)
 *   {
 *     if (target is not valid for source)
 *     {
 *       return 'Invalid Target';
 *     }
 *   }
 *
 *   // "Supercall"
 *   return mxGraph.prototype.getEdgeValidationError.apply(this, arguments);
 * }
 * (end)
 *
 * Parameters:
 *
 * edge - <mxCell> that represents the edge to validate.
 * source - <mxCell> that represents the source terminal.
 * target - <mxCell> that represents the target terminal.
 */
mxGraph.prototype.getEdgeValidationError = function (edge, source, target) {
  if (edge != null && !this.isAllowDanglingEdges() && (source == null || target == null)) {
    return '';
  }

  if (edge != null && this.model.getTerminal(edge, true) == null && this.model.getTerminal(edge, false) == null) {
    return null;
  }

  // Checks if we're dealing with a loop
  if (!this.allowLoops && source == target && source != null) {
    return '';
  }

  // Checks if the connection is generally allowed
  if (!this.isValidConnection(source, target)) {
    return '';
  }

  if (source != null && target != null) {
    var error = '';

    // Checks if the cells are already connected
    // and adds an error message if required
    if (!this.multigraph) {
      var tmp = this.model.getEdgesBetween(source, target, true);

      // Checks if the source and target are not connected by another edge
      if (tmp.length > 1 || (tmp.length == 1 && tmp[0] != edge)) {
        error += (mxResources.get(this.alreadyConnectedResource) || this.alreadyConnectedResource) + '\n';
      }
    }

    // Gets the number of outgoing edges from the source
    // and the number of incoming edges from the target
    // without counting the edge being currently changed.
    var sourceOut = this.model.getDirectedEdgeCount(source, true, edge);
    var targetIn = this.model.getDirectedEdgeCount(target, false, edge);

    // Checks the change against each multiplicity rule
    if (this.multiplicities != null) {
      for (var i = 0; i < this.multiplicities.length; i++) {
        var err = this.multiplicities[i].check(this, edge, source, target, sourceOut, targetIn);

        if (err != null) {
          error += err;
        }
      }
    }

    // Validates the source and target terminals independently
    var err = this.validateEdge(edge, source, target);

    if (err != null) {
      error += err;
    }

    return error.length > 0 ? error : null;
  }

  return this.allowDanglingEdges ? null : '';
};

/**
 * Function: validateEdge
 *
 * Hook method for subclassers to return an error message for the given
 * edge and terminals. This implementation returns null.
 *
 * Parameters:
 *
 * edge - <mxCell> that represents the edge to validate.
 * source - <mxCell> that represents the source terminal.
 * target - <mxCell> that represents the target terminal.
 */
mxGraph.prototype.validateEdge = function (edge, source, target) {
  return null;
};

/**
 * Function: validateGraph
 *
 * Validates the graph by validating each descendant of the given cell or
 * the root of the model. Context is an object that contains the validation
 * state for the complete validation run. The validation errors are
 * attached to their cells using <setCellWarning>. Returns null in the case of
 * successful validation or an array of strings (warnings) in the case of
 * failed validations.
 *
 * Paramters:
 *
 * cell - Optional <mxCell> to start the validation recursion. Default is
 * the graph root.
 * context - Object that represents the global validation state.
 */
mxGraph.prototype.validateGraph = function (cell, context) {
  cell = cell != null ? cell : this.model.getRoot();
  context = context != null ? context : new Object();

  var isValid = true;
  var childCount = this.model.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    var tmp = this.model.getChildAt(cell, i);
    var ctx = context;

    if (this.isValidRoot(tmp)) {
      ctx = new Object();
    }

    var warn = this.validateGraph(tmp, ctx);

    if (warn != null) {
      this.setCellWarning(tmp, warn.replace(/\n/g, '<br>'));
    } else {
      this.setCellWarning(tmp, null);
    }

    isValid = isValid && warn == null;
  }

  var warning = '';

  // Adds error for invalid children if collapsed (children invisible)
  if (this.isCellCollapsed(cell) && !isValid) {
    warning += (mxResources.get(this.containsValidationErrorsResource) || this.containsValidationErrorsResource) + '\n';
  }

  // Checks edges and cells using the defined multiplicities
  if (this.model.isEdge(cell)) {
    warning +=
      this.getEdgeValidationError(cell, this.model.getTerminal(cell, true), this.model.getTerminal(cell, false)) || '';
  } else {
    warning += this.getCellValidationError(cell) || '';
  }

  // Checks custom validation rules
  var err = this.validateCell(cell, context);

  if (err != null) {
    warning += err;
  }

  // Updates the display with the warning icons
  // before any potential alerts are displayed.
  // LATER: Move this into addCellOverlay. Redraw
  // should check if overlay was added or removed.
  if (this.model.getParent(cell) == null) {
    this.view.validate();
  }

  return warning.length > 0 || !isValid ? warning : null;
};

/**
 * Function: getCellValidationError
 *
 * Checks all <multiplicities> that cannot be enforced while the graph is
 * being modified, namely, all multiplicities that require a minimum of
 * 1 edge.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the multiplicities should be checked.
 */
mxGraph.prototype.getCellValidationError = function (cell) {
  var outCount = this.model.getDirectedEdgeCount(cell, true);
  var inCount = this.model.getDirectedEdgeCount(cell, false);
  var value = this.model.getValue(cell);
  var error = '';

  if (this.multiplicities != null) {
    for (var i = 0; i < this.multiplicities.length; i++) {
      var rule = this.multiplicities[i];

      if (
        rule.source &&
        mxUtils.isNode(value, rule.type, rule.attr, rule.value) &&
        (outCount > rule.max || outCount < rule.min)
      ) {
        error += rule.countError + '\n';
      } else if (
        !rule.source &&
        mxUtils.isNode(value, rule.type, rule.attr, rule.value) &&
        (inCount > rule.max || inCount < rule.min)
      ) {
        error += rule.countError + '\n';
      }
    }
  }

  return error.length > 0 ? error : null;
};

/**
 * Function: validateCell
 *
 * Hook method for subclassers to return an error message for the given
 * cell and validation context. This implementation returns null. Any HTML
 * breaks will be converted to linefeeds in the calling method.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the cell to validate.
 * context - Object that represents the global validation state.
 */
mxGraph.prototype.validateCell = function (cell, context) {
  return null;
};

/**
 * Group: Graph appearance
 */

/**
 * Function: getBackgroundImage
 *
 * Returns the <backgroundImage> as an <mxImage>.
 */
mxGraph.prototype.getBackgroundImage = function () {
  return this.backgroundImage;
};

/**
 * Function: setBackgroundImage
 *
 * Sets the new <backgroundImage>.
 *
 * Parameters:
 *
 * image - New <mxImage> to be used for the background.
 */
mxGraph.prototype.setBackgroundImage = function (image) {
  this.backgroundImage = image;
};

/**
 * Function: getFoldingImage
 *
 * Returns the <mxImage> used to display the collapsed state of
 * the specified cell state. This returns null for all edges.
 */
mxGraph.prototype.getFoldingImage = function (state) {
  if (state != null && this.foldingEnabled && !this.getModel().isEdge(state.cell)) {
    var tmp = this.isCellCollapsed(state.cell);

    if (this.isCellFoldable(state.cell, !tmp)) {
      return tmp ? this.collapsedImage : this.expandedImage;
    }
  }

  return null;
};

/**
 * Function: convertValueToString
 *
 * Returns the textual representation for the given cell. This
 * implementation returns the nodename or string-representation of the user
 * object.
 *
 * Example:
 *
 * The following returns the label attribute from the cells user
 * object if it is an XML node.
 *
 * (code)
 * graph.convertValueToString = function(cell)
 * {
 * 	return cell.getAttribute('label');
 * }
 * (end)
 *
 * See also: <cellLabelChanged>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose textual representation should be returned.
 */
mxGraph.prototype.convertValueToString = function (cell) {
  var value = this.model.getValue(cell);

  if (value != null) {
    if (mxUtils.isNode(value)) {
      return value.nodeName;
    } else if (typeof value.toString == 'function') {
      return value.toString();
    }
  }

  return '';
};

/**
 * Function: getLabel
 *
 * Returns a string or DOM node that represents the label for the given
 * cell. This implementation uses <convertValueToString> if <labelsVisible>
 * is true. Otherwise it returns an empty string.
 *
 * To truncate a label to match the size of the cell, the following code
 * can be used.
 *
 * (code)
 * graph.getLabel = function(cell)
 * {
 *   var label = mxGraph.prototype.getLabel.apply(this, arguments);
 *
 *   if (label != null && this.model.isVertex(cell))
 *   {
 *     var geo = this.getCellGeometry(cell);
 *
 *     if (geo != null)
 *     {
 *       var max = parseInt(geo.width / 8);
 *
 *       if (label.length > max)
 *       {
 *         label = label.substring(0, max)+'...';
 *       }
 *     }
 *   }
 *   return mxUtils.htmlEntities(label);
 * }
 * (end)
 *
 * A resize listener is needed in the graph to force a repaint of the label
 * after a resize.
 *
 * (code)
 * graph.addListener(mxEvent.RESIZE_CELLS, function(sender, evt)
 * {
 *   var cells = evt.getProperty('cells');
 *
 *   for (var i = 0; i < cells.length; i++)
 *   {
 *     this.view.removeState(cells[i]);
 *   }
 * });
 * (end)
 *
 * Parameters:
 *
 * cell - <mxCell> whose label should be returned.
 */
mxGraph.prototype.getLabel = function (cell) {
  var result = '';

  if (this.labelsVisible && cell != null) {
    var style = this.getCurrentCellStyle(cell);

    if (!mxUtils.getValue(style, mxConstants.STYLE_NOLABEL, false)) {
      result = this.convertValueToString(cell);
    }
  }

  return result;
};

/**
 * Function: isHtmlLabel
 *
 * Returns true if the label must be rendered as HTML markup. The default
 * implementation returns <htmlLabels>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose label should be displayed as HTML markup.
 */
mxGraph.prototype.isHtmlLabel = function (cell) {
  return this.isHtmlLabels();
};

/**
 * Function: isHtmlLabels
 *
 * Returns <htmlLabels>.
 */
mxGraph.prototype.isHtmlLabels = function () {
  return this.htmlLabels;
};

/**
 * Function: setHtmlLabels
 *
 * Sets <htmlLabels>.
 */
mxGraph.prototype.setHtmlLabels = function (value) {
  this.htmlLabels = value;
};

/**
 * Function: isWrapping
 *
 * This enables wrapping for HTML labels.
 *
 * Returns true if no white-space CSS style directive should be used for
 * displaying the given cells label. This implementation returns true if
 * <mxConstants.STYLE_WHITE_SPACE> in the style of the given cell is 'wrap'.
 *
 * This is used as a workaround for IE ignoring the white-space directive
 * of child elements if the directive appears in a parent element. It
 * should be overridden to return true if a white-space directive is used
 * in the HTML markup that represents the given cells label. In order for
 * HTML markup to work in labels, <isHtmlLabel> must also return true
 * for the given cell.
 *
 * Example:
 *
 * (code)
 * graph.getLabel = function(cell)
 * {
 *   var tmp = mxGraph.prototype.getLabel.apply(this, arguments); // "supercall"
 *
 *   if (this.model.isEdge(cell))
 *   {
 *     tmp = '<div style="width: 150px; white-space:normal;">'+tmp+'</div>';
 *   }
 *
 *   return tmp;
 * }
 *
 * graph.isWrapping = function(state)
 * {
 * 	 return this.model.isEdge(state.cell);
 * }
 * (end)
 *
 * Makes sure no edge label is wider than 150 pixels, otherwise the content
 * is wrapped. Note: No width must be specified for wrapped vertex labels as
 * the vertex defines the width in its geometry.
 *
 * Parameters:
 *
 * state - <mxCell> whose label should be wrapped.
 */
mxGraph.prototype.isWrapping = function (cell) {
  return this.getCurrentCellStyle(cell)[mxConstants.STYLE_WHITE_SPACE] == 'wrap';
};

/**
 * Function: isLabelClipped
 *
 * Returns true if the overflow portion of labels should be hidden. If this
 * returns true then vertex labels will be clipped to the size of the vertices.
 * This implementation returns true if <mxConstants.STYLE_OVERFLOW> in the
 * style of the given cell is 'hidden'.
 *
 * Parameters:
 *
 * state - <mxCell> whose label should be clipped.
 */
mxGraph.prototype.isLabelClipped = function (cell) {
  return this.getCurrentCellStyle(cell)[mxConstants.STYLE_OVERFLOW] == 'hidden';
};

/**
 * Function: getTooltip
 *
 * Returns the string or DOM node that represents the tooltip for the given
 * state, node and coordinate pair. This implementation checks if the given
 * node is a folding icon or overlay and returns the respective tooltip. If
 * this does not result in a tooltip, the handler for the cell is retrieved
 * from <selectionCellsHandler> and the optional getTooltipForNode method is
 * called. If no special tooltip exists here then <getTooltipForCell> is used
 * with the cell in the given state as the argument to return a tooltip for the
 * given state.
 *
 * Parameters:
 *
 * state - <mxCellState> whose tooltip should be returned.
 * node - DOM node that is currently under the mouse.
 * x - X-coordinate of the mouse.
 * y - Y-coordinate of the mouse.
 */
mxGraph.prototype.getTooltip = function (state, node, x, y) {
  var tip = null;

  if (state != null) {
    // Checks if the mouse is over the folding icon
    if (state.control != null && (node == state.control.node || node.parentNode == state.control.node)) {
      tip = this.collapseExpandResource;
      tip = mxUtils.htmlEntities(mxResources.get(tip) || tip).replace(/\\n/g, '<br>');
    }

    if (tip == null && state.overlays != null) {
      state.overlays.visit(function (id, shape) {
        // LATER: Exit loop if tip is not null
        if (tip == null && (node == shape.node || node.parentNode == shape.node)) {
          tip = shape.overlay.toString();
        }
      });
    }

    if (tip == null) {
      var handler = this.selectionCellsHandler.getHandler(state.cell);

      if (handler != null && typeof handler.getTooltipForNode == 'function') {
        tip = handler.getTooltipForNode(node);
      }
    }

    if (tip == null) {
      tip = this.getTooltipForCell(state.cell);
    }
  }

  return tip;
};

/**
 * Function: getTooltipForCell
 *
 * Returns the string or DOM node to be used as the tooltip for the given
 * cell. This implementation uses the cells getTooltip function if it
 * exists, or else it returns <convertValueToString> for the cell.
 *
 * Example:
 *
 * (code)
 * graph.getTooltipForCell = function(cell)
 * {
 *   return 'Hello, World!';
 * }
 * (end)
 *
 * Replaces all tooltips with the string Hello, World!
 *
 * Parameters:
 *
 * cell - <mxCell> whose tooltip should be returned.
 */
mxGraph.prototype.getTooltipForCell = function (cell) {
  var tip = null;

  if (cell != null && cell.getTooltip != null) {
    tip = cell.getTooltip();
  } else {
    tip = this.convertValueToString(cell);
  }

  return tip;
};

/**
 * Function: getLinkForCell
 *
 * Returns the string to be used as the link for the given cell. This
 * implementation returns null.
 *
 * Parameters:
 *
 * cell - <mxCell> whose tooltip should be returned.
 */
mxGraph.prototype.getLinkForCell = function (cell) {
  return null;
};

/**
 * Function: getCursorForMouseEvent
 *
 * Returns the cursor value to be used for the CSS of the shape for the
 * given event. This implementation calls <getCursorForCell>.
 *
 * Parameters:
 *
 * me - <mxMouseEvent> whose cursor should be returned.
 */
mxGraph.prototype.getCursorForMouseEvent = function (me) {
  return this.getCursorForCell(me.getCell());
};

/**
 * Function: getCursorForCell
 *
 * Returns the cursor value to be used for the CSS of the shape for the
 * given cell. This implementation returns null.
 *
 * Parameters:
 *
 * cell - <mxCell> whose cursor should be returned.
 */
mxGraph.prototype.getCursorForCell = function (cell) {
  return null;
};

/**
 * Function: getStartSize
 *
 * Returns the start size of the given swimlane, that is, the width or
 * height of the part that contains the title, depending on the
 * horizontal style. The return value is an <mxRectangle> with either
 * width or height set as appropriate.
 *
 * Parameters:
 *
 * swimlane - <mxCell> whose start size should be returned.
 * ignoreState - Optional boolean that specifies if cell state should be ignored.
 */
mxGraph.prototype.getStartSize = function (swimlane, ignoreState) {
  var result = new mxRectangle();
  var style = this.getCurrentCellStyle(swimlane, ignoreState);
  var size = parseInt(mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));

  if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true)) {
    result.height = size;
  } else {
    result.width = size;
  }

  return result;
};

/**
 * Function: getSwimlaneDirection
 *
 * Returns the direction for the given swimlane style.
 */
mxGraph.prototype.getSwimlaneDirection = function (style) {
  var dir = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
  var flipH = mxUtils.getValue(style, mxConstants.STYLE_FLIPH, 0) == 1;
  var flipV = mxUtils.getValue(style, mxConstants.STYLE_FLIPV, 0) == 1;
  var h = mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true);
  var n = h ? 0 : 3;

  if (dir == mxConstants.DIRECTION_NORTH) {
    n--;
  } else if (dir == mxConstants.DIRECTION_WEST) {
    n += 2;
  } else if (dir == mxConstants.DIRECTION_SOUTH) {
    n += 1;
  }

  var mod = mxUtils.mod(n, 2);

  if (flipH && mod == 1) {
    n += 2;
  }

  if (flipV && mod == 0) {
    n += 2;
  }

  return [
    mxConstants.DIRECTION_NORTH,
    mxConstants.DIRECTION_EAST,
    mxConstants.DIRECTION_SOUTH,
    mxConstants.DIRECTION_WEST,
  ][mxUtils.mod(n, 4)];
};

/**
 * Function: getActualStartSize
 *
 * Returns the actual start size of the given swimlane taking into account
 * direction and horizontal and vertial flip styles. The start size is
 * returned as an <mxRectangle> where top, left, bottom, right start sizes
 * are returned as x, y, height and width, respectively.
 *
 * Parameters:
 *
 * swimlane - <mxCell> whose start size should be returned.
 * ignoreState - Optional boolean that specifies if cell state should be ignored.
 */
mxGraph.prototype.getActualStartSize = function (swimlane, ignoreState) {
  var result = new mxRectangle();

  if (this.isSwimlane(swimlane, ignoreState)) {
    var style = this.getCurrentCellStyle(swimlane, ignoreState);
    var size = parseInt(mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
    var dir = this.getSwimlaneDirection(style);

    if (dir == mxConstants.DIRECTION_NORTH) {
      result.y = size;
    } else if (dir == mxConstants.DIRECTION_WEST) {
      result.x = size;
    } else if (dir == mxConstants.DIRECTION_SOUTH) {
      result.height = size;
    } else {
      result.width = size;
    }
  }

  return result;
};

/**
 * Function: getImage
 *
 * Returns the image URL for the given cell state. This implementation
 * returns the value stored under <mxConstants.STYLE_IMAGE> in the cell
 * style.
 *
 * Parameters:
 *
 * state - <mxCellState> whose image URL should be returned.
 */
mxGraph.prototype.getImage = function (state) {
  return state != null && state.style != null ? state.style[mxConstants.STYLE_IMAGE] : null;
};

/**
 * Function: isTransparentState
 *
 * Returns true if the given state has no stroke- or fillcolor and no image.
 *
 * Parameters:
 *
 * state - <mxCellState> to check.
 */
mxGraph.prototype.isTransparentState = function (state) {
  var result = false;

  if (state != null) {
    var stroke = mxUtils.getValue(state.style, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE);
    var fill = mxUtils.getValue(state.style, mxConstants.STYLE_FILLCOLOR, mxConstants.NONE);

    result = stroke == mxConstants.NONE && fill == mxConstants.NONE && this.getImage(state) == null;
  }

  return result;
};

/**
 * Function: getVerticalAlign
 *
 * Returns the vertical alignment for the given cell state. This
 * implementation returns the value stored under
 * <mxConstants.STYLE_VERTICAL_ALIGN> in the cell style.
 *
 * Parameters:
 *
 * state - <mxCellState> whose vertical alignment should be
 * returned.
 */
mxGraph.prototype.getVerticalAlign = function (state) {
  return state != null && state.style != null
    ? state.style[mxConstants.STYLE_VERTICAL_ALIGN] || mxConstants.ALIGN_MIDDLE
    : null;
};

/**
 * Function: getIndicatorColor
 *
 * Returns the indicator color for the given cell state. This
 * implementation returns the value stored under
 * <mxConstants.STYLE_INDICATOR_COLOR> in the cell style.
 *
 * Parameters:
 *
 * state - <mxCellState> whose indicator color should be
 * returned.
 */
mxGraph.prototype.getIndicatorColor = function (state) {
  return state != null && state.style != null ? state.style[mxConstants.STYLE_INDICATOR_COLOR] : null;
};

/**
 * Function: getIndicatorGradientColor
 *
 * Returns the indicator gradient color for the given cell state. This
 * implementation returns the value stored under
 * <mxConstants.STYLE_INDICATOR_GRADIENTCOLOR> in the cell style.
 *
 * Parameters:
 *
 * state - <mxCellState> whose indicator gradient color should be
 * returned.
 */
mxGraph.prototype.getIndicatorGradientColor = function (state) {
  return state != null && state.style != null ? state.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR] : null;
};

/**
 * Function: getIndicatorShape
 *
 * Returns the indicator shape for the given cell state. This
 * implementation returns the value stored under
 * <mxConstants.STYLE_INDICATOR_SHAPE> in the cell style.
 *
 * Parameters:
 *
 * state - <mxCellState> whose indicator shape should be returned.
 */
mxGraph.prototype.getIndicatorShape = function (state) {
  return state != null && state.style != null ? state.style[mxConstants.STYLE_INDICATOR_SHAPE] : null;
};

/**
 * Function: getIndicatorImage
 *
 * Returns the indicator image for the given cell state. This
 * implementation returns the value stored under
 * <mxConstants.STYLE_INDICATOR_IMAGE> in the cell style.
 *
 * Parameters:
 *
 * state - <mxCellState> whose indicator image should be returned.
 */
mxGraph.prototype.getIndicatorImage = function (state) {
  return state != null && state.style != null ? state.style[mxConstants.STYLE_INDICATOR_IMAGE] : null;
};

/**
 * Function: getBorder
 *
 * Returns the value of <border>.
 */
mxGraph.prototype.getBorder = function () {
  return this.border;
};

/**
 * Function: setBorder
 *
 * Sets the value of <border>.
 *
 * Parameters:
 *
 * value - Positive integer that represents the border to be used.
 */
mxGraph.prototype.setBorder = function (value) {
  this.border = value;
};

/**
 * Function: isSwimlane
 *
 * Returns true if the given cell is a swimlane in the graph. A swimlane is
 * a container cell with some specific behaviour. This implementation
 * checks if the shape associated with the given cell is a <mxSwimlane>.
 *
 * Parameters:
 *
 * cell - <mxCell> to be checked.
 * ignoreState - Optional boolean that specifies if the cell state should be ignored.
 */
mxGraph.prototype.isSwimlane = function (cell, ignoreState) {
  if (cell != null && this.model.getParent(cell) != this.model.getRoot() && !this.model.isEdge(cell)) {
    return this.getCurrentCellStyle(cell, ignoreState)[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_SWIMLANE;
  }

  return false;
};

/**
 * Group: Graph behaviour
 */

/**
 * Function: isResizeContainer
 *
 * Returns <resizeContainer>.
 */
mxGraph.prototype.isResizeContainer = function () {
  return this.resizeContainer;
};

/**
 * Function: setResizeContainer
 *
 * Sets <resizeContainer>.
 *
 * Parameters:
 *
 * value - Boolean indicating if the container should be resized.
 */
mxGraph.prototype.setResizeContainer = function (value) {
  this.resizeContainer = value;
};

/**
 * Function: isEnabled
 *
 * Returns true if the graph is <enabled>.
 */
mxGraph.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Specifies if the graph should allow any interactions. This
 * implementation updates <enabled>.
 *
 * Parameters:
 *
 * value - Boolean indicating if the graph should be enabled.
 */
mxGraph.prototype.setEnabled = function (value) {
  this.enabled = value;
};

/**
 * Function: isEscapeEnabled
 *
 * Returns <escapeEnabled>.
 */
mxGraph.prototype.isEscapeEnabled = function () {
  return this.escapeEnabled;
};

/**
 * Function: setEscapeEnabled
 *
 * Sets <escapeEnabled>.
 *
 * Parameters:
 *
 * enabled - Boolean indicating if escape should be enabled.
 */
mxGraph.prototype.setEscapeEnabled = function (value) {
  this.escapeEnabled = value;
};

/**
 * Function: isInvokesStopCellEditing
 *
 * Returns <invokesStopCellEditing>.
 */
mxGraph.prototype.isInvokesStopCellEditing = function () {
  return this.invokesStopCellEditing;
};

/**
 * Function: setInvokesStopCellEditing
 *
 * Sets <invokesStopCellEditing>.
 */
mxGraph.prototype.setInvokesStopCellEditing = function (value) {
  this.invokesStopCellEditing = value;
};

/**
 * Function: isEnterStopsCellEditing
 *
 * Returns <enterStopsCellEditing>.
 */
mxGraph.prototype.isEnterStopsCellEditing = function () {
  return this.enterStopsCellEditing;
};

/**
 * Function: setEnterStopsCellEditing
 *
 * Sets <enterStopsCellEditing>.
 */
mxGraph.prototype.setEnterStopsCellEditing = function (value) {
  this.enterStopsCellEditing = value;
};

/**
 * Function: isCellLocked
 *
 * Returns true if the given cell may not be moved, sized, bended,
 * disconnected, edited or selected. This implementation returns true for
 * all vertices with a relative geometry if <locked> is false.
 *
 * Parameters:
 *
 * cell - <mxCell> whose locked state should be returned.
 */
mxGraph.prototype.isCellLocked = function (cell) {
  var geometry = this.model.getGeometry(cell);

  return this.isCellsLocked() || (geometry != null && this.model.isVertex(cell) && geometry.relative);
};

/**
 * Function: isCellsLocked
 *
 * Returns true if the given cell may not be moved, sized, bended,
 * disconnected, edited or selected. This implementation returns true for
 * all vertices with a relative geometry if <locked> is false.
 *
 * Parameters:
 *
 * cell - <mxCell> whose locked state should be returned.
 */
mxGraph.prototype.isCellsLocked = function () {
  return this.cellsLocked;
};

/**
 * Function: setCellsLocked
 *
 * Sets if any cell may be moved, sized, bended, disconnected, edited or
 * selected.
 *
 * Parameters:
 *
 * value - Boolean that defines the new value for <cellsLocked>.
 */
mxGraph.prototype.setCellsLocked = function (value) {
  this.cellsLocked = value;
};

/**
 * Function: getCloneableCells
 *
 * Returns the cells which may be exported in the given array of cells.
 */
mxGraph.prototype.getCloneableCells = function (cells) {
  return this.model.filterCells(
    cells,
    mxUtils.bind(this, function (cell) {
      return this.isCellCloneable(cell);
    })
  );
};

/**
 * Function: isCellCloneable
 *
 * Returns true if the given cell is cloneable. This implementation returns
 * <isCellsCloneable> for all cells unless a cell style specifies
 * <mxConstants.STYLE_CLONEABLE> to be 0.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> whose cloneable state should be returned.
 */
mxGraph.prototype.isCellCloneable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return this.isCellsCloneable() && style[mxConstants.STYLE_CLONEABLE] != 0;
};

/**
 * Function: isCellsCloneable
 *
 * Returns <cellsCloneable>, that is, if the graph allows cloning of cells
 * by using control-drag.
 */
mxGraph.prototype.isCellsCloneable = function () {
  return this.cellsCloneable;
};

/**
 * Function: setCellsCloneable
 *
 * Specifies if the graph should allow cloning of cells by holding down the
 * control key while cells are being moved. This implementation updates
 * <cellsCloneable>.
 *
 * Parameters:
 *
 * value - Boolean indicating if the graph should be cloneable.
 */
mxGraph.prototype.setCellsCloneable = function (value) {
  this.cellsCloneable = value;
};

/**
 * Function: getExportableCells
 *
 * Returns the cells which may be exported in the given array of cells.
 */
mxGraph.prototype.getExportableCells = function (cells) {
  return this.model.filterCells(
    cells,
    mxUtils.bind(this, function (cell) {
      return this.canExportCell(cell);
    })
  );
};

/**
 * Function: canExportCell
 *
 * Returns true if the given cell may be exported to the clipboard. This
 * implementation returns <exportEnabled> for all cells.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the cell to be exported.
 */
mxGraph.prototype.canExportCell = function (cell) {
  return this.exportEnabled;
};

/**
 * Function: getImportableCells
 *
 * Returns the cells which may be imported in the given array of cells.
 */
mxGraph.prototype.getImportableCells = function (cells) {
  return this.model.filterCells(
    cells,
    mxUtils.bind(this, function (cell) {
      return this.canImportCell(cell);
    })
  );
};

/**
 * Function: canImportCell
 *
 * Returns true if the given cell may be imported from the clipboard.
 * This implementation returns <importEnabled> for all cells.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the cell to be imported.
 */
mxGraph.prototype.canImportCell = function (cell) {
  return this.importEnabled;
};

/**
 * Function: isCellSelectable
 *
 * Returns true if the given cell is selectable. This implementation
 * returns <cellsSelectable>.
 *
 * To add a new style for making cells (un)selectable, use the following code.
 *
 * (code)
 * mxGraph.prototype.isCellSelectable = function(cell)
 * {
 *   var style = this.getCurrentCellStyle(cell);
 *
 *   return this.isCellsSelectable() && !this.isCellLocked(cell) && style['selectable'] != 0;
 * };
 * (end)
 *
 * You can then use the new style as shown in this example.
 *
 * (code)
 * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'selectable=0');
 * (end)
 *
 * Parameters:
 *
 * cell - <mxCell> whose selectable state should be returned.
 */
mxGraph.prototype.isCellSelectable = function (cell) {
  return this.isCellsSelectable();
};

/**
 * Function: isCellsSelectable
 *
 * Returns <cellsSelectable>.
 */
mxGraph.prototype.isCellsSelectable = function () {
  return this.cellsSelectable;
};

/**
 * Function: setCellsSelectable
 *
 * Sets <cellsSelectable>.
 */
mxGraph.prototype.setCellsSelectable = function (value) {
  this.cellsSelectable = value;
};

/**
 * Function: getDeletableCells
 *
 * Returns the cells which may be exported in the given array of cells.
 */
mxGraph.prototype.getDeletableCells = function (cells) {
  return this.model.filterCells(
    cells,
    mxUtils.bind(this, function (cell) {
      return this.isCellDeletable(cell);
    })
  );
};

/**
 * Function: isCellDeletable
 *
 * Returns true if the given cell is moveable. This returns
 * <cellsDeletable> for all given cells if a cells style does not specify
 * <mxConstants.STYLE_DELETABLE> to be 0.
 *
 * Parameters:
 *
 * cell - <mxCell> whose deletable state should be returned.
 */
mxGraph.prototype.isCellDeletable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return this.isCellsDeletable() && style[mxConstants.STYLE_DELETABLE] != 0;
};

/**
 * Function: isCellsDeletable
 *
 * Returns <cellsDeletable>.
 */
mxGraph.prototype.isCellsDeletable = function () {
  return this.cellsDeletable;
};

/**
 * Function: setCellsDeletable
 *
 * Sets <cellsDeletable>.
 *
 * Parameters:
 *
 * value - Boolean indicating if the graph should allow deletion of cells.
 */
mxGraph.prototype.setCellsDeletable = function (value) {
  this.cellsDeletable = value;
};

/**
 * Function: isLabelMovable
 *
 * Returns true if the given edges's label is moveable. This returns
 * <movable> for all given cells if <isLocked> does not return true
 * for the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> whose label should be moved.
 */
mxGraph.prototype.isLabelMovable = function (cell) {
  return (
    !this.isCellLocked(cell) &&
    ((this.model.isEdge(cell) && this.edgeLabelsMovable) || (this.model.isVertex(cell) && this.vertexLabelsMovable))
  );
};

/**
 * Function: isCellRotatable
 *
 * Returns true if the given cell is rotatable. This returns true for the given
 * cell if its style does not specify <mxConstants.STYLE_ROTATABLE> to be 0.
 *
 * Parameters:
 *
 * cell - <mxCell> whose rotatable state should be returned.
 */
mxGraph.prototype.isCellRotatable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return style[mxConstants.STYLE_ROTATABLE] != 0;
};

/**
 * Function: getMovableCells
 *
 * Returns the cells which are movable in the given array of cells.
 */
mxGraph.prototype.getMovableCells = function (cells) {
  return this.model.filterCells(
    cells,
    mxUtils.bind(this, function (cell) {
      return this.isCellMovable(cell);
    })
  );
};

/**
 * Function: isCellMovable
 *
 * Returns true if the given cell is moveable. This returns <cellsMovable>
 * for all given cells if <isCellLocked> does not return true for the given
 * cell and its style does not specify <mxConstants.STYLE_MOVABLE> to be 0.
 *
 * Parameters:
 *
 * cell - <mxCell> whose movable state should be returned.
 */
mxGraph.prototype.isCellMovable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return this.isCellsMovable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_MOVABLE] != 0;
};

/**
 * Function: isCellsMovable
 *
 * Returns <cellsMovable>.
 */
mxGraph.prototype.isCellsMovable = function () {
  return this.cellsMovable;
};

/**
 * Function: setCellsMovable
 *
 * Specifies if the graph should allow moving of cells. This implementation
 * updates <cellsMsovable>.
 *
 * Parameters:
 *
 * value - Boolean indicating if the graph should allow moving of cells.
 */
mxGraph.prototype.setCellsMovable = function (value) {
  this.cellsMovable = value;
};

/**
 * Function: isGridEnabled
 *
 * Returns <gridEnabled> as a boolean.
 */
mxGraph.prototype.isGridEnabled = function () {
  return this.gridEnabled;
};

/**
 * Function: setGridEnabled
 *
 * Specifies if the grid should be enabled.
 *
 * Parameters:
 *
 * value - Boolean indicating if the grid should be enabled.
 */
mxGraph.prototype.setGridEnabled = function (value) {
  this.gridEnabled = value;
};

/**
 * Function: isPortsEnabled
 *
 * Returns <portsEnabled> as a boolean.
 */
mxGraph.prototype.isPortsEnabled = function () {
  return this.portsEnabled;
};

/**
 * Function: setPortsEnabled
 *
 * Specifies if the ports should be enabled.
 *
 * Parameters:
 *
 * value - Boolean indicating if the ports should be enabled.
 */
mxGraph.prototype.setPortsEnabled = function (value) {
  this.portsEnabled = value;
};

/**
 * Function: getGridSize
 *
 * Returns <gridSize>.
 */
mxGraph.prototype.getGridSize = function () {
  return this.gridSize;
};

/**
 * Function: setGridSize
 *
 * Sets <gridSize>.
 */
mxGraph.prototype.setGridSize = function (value) {
  this.gridSize = value;
};

/**
 * Function: getTolerance
 *
 * Returns <tolerance>.
 */
mxGraph.prototype.getTolerance = function () {
  return this.tolerance;
};

/**
 * Function: setTolerance
 *
 * Sets <tolerance>.
 */
mxGraph.prototype.setTolerance = function (value) {
  this.tolerance = value;
};

/**
 * Function: isVertexLabelsMovable
 *
 * Returns <vertexLabelsMovable>.
 */
mxGraph.prototype.isVertexLabelsMovable = function () {
  return this.vertexLabelsMovable;
};

/**
 * Function: setVertexLabelsMovable
 *
 * Sets <vertexLabelsMovable>.
 */
mxGraph.prototype.setVertexLabelsMovable = function (value) {
  this.vertexLabelsMovable = value;
};

/**
 * Function: isEdgeLabelsMovable
 *
 * Returns <edgeLabelsMovable>.
 */
mxGraph.prototype.isEdgeLabelsMovable = function () {
  return this.edgeLabelsMovable;
};

/**
 * Function: isEdgeLabelsMovable
 *
 * Sets <edgeLabelsMovable>.
 */
mxGraph.prototype.setEdgeLabelsMovable = function (value) {
  this.edgeLabelsMovable = value;
};

/**
 * Function: isSwimlaneNesting
 *
 * Returns <swimlaneNesting> as a boolean.
 */
mxGraph.prototype.isSwimlaneNesting = function () {
  return this.swimlaneNesting;
};

/**
 * Function: setSwimlaneNesting
 *
 * Specifies if swimlanes can be nested by drag and drop. This is only
 * taken into account if dropEnabled is true.
 *
 * Parameters:
 *
 * value - Boolean indicating if swimlanes can be nested.
 */
mxGraph.prototype.setSwimlaneNesting = function (value) {
  this.swimlaneNesting = value;
};

/**
 * Function: isSwimlaneSelectionEnabled
 *
 * Returns <swimlaneSelectionEnabled> as a boolean.
 */
mxGraph.prototype.isSwimlaneSelectionEnabled = function () {
  return this.swimlaneSelectionEnabled;
};

/**
 * Function: setSwimlaneSelectionEnabled
 *
 * Specifies if swimlanes should be selected if the mouse is released
 * over their content area.
 *
 * Parameters:
 *
 * value - Boolean indicating if swimlanes content areas
 * should be selected when the mouse is released over them.
 */
mxGraph.prototype.setSwimlaneSelectionEnabled = function (value) {
  this.swimlaneSelectionEnabled = value;
};

/**
 * Function: isMultigraph
 *
 * Returns <multigraph> as a boolean.
 */
mxGraph.prototype.isMultigraph = function () {
  return this.multigraph;
};

/**
 * Function: setMultigraph
 *
 * Specifies if the graph should allow multiple connections between the
 * same pair of vertices.
 *
 * Parameters:
 *
 * value - Boolean indicating if the graph allows multiple connections
 * between the same pair of vertices.
 */
mxGraph.prototype.setMultigraph = function (value) {
  this.multigraph = value;
};

/**
 * Function: isAllowLoops
 *
 * Returns <allowLoops> as a boolean.
 */
mxGraph.prototype.isAllowLoops = function () {
  return this.allowLoops;
};

/**
 * Function: setAllowDanglingEdges
 *
 * Specifies if dangling edges are allowed, that is, if edges are allowed
 * that do not have a source and/or target terminal defined.
 *
 * Parameters:
 *
 * value - Boolean indicating if dangling edges are allowed.
 */
mxGraph.prototype.setAllowDanglingEdges = function (value) {
  this.allowDanglingEdges = value;
};

/**
 * Function: isAllowDanglingEdges
 *
 * Returns <allowDanglingEdges> as a boolean.
 */
mxGraph.prototype.isAllowDanglingEdges = function () {
  return this.allowDanglingEdges;
};

/**
 * Function: setConnectableEdges
 *
 * Specifies if edges should be connectable.
 *
 * Parameters:
 *
 * value - Boolean indicating if edges should be connectable.
 */
mxGraph.prototype.setConnectableEdges = function (value) {
  this.connectableEdges = value;
};

/**
 * Function: isConnectableEdges
 *
 * Returns <connectableEdges> as a boolean.
 */
mxGraph.prototype.isConnectableEdges = function () {
  return this.connectableEdges;
};

/**
 * Function: setCloneInvalidEdges
 *
 * Specifies if edges should be inserted when cloned but not valid wrt.
 * <getEdgeValidationError>. If false such edges will be silently ignored.
 *
 * Parameters:
 *
 * value - Boolean indicating if cloned invalid edges should be
 * inserted into the graph or ignored.
 */
mxGraph.prototype.setCloneInvalidEdges = function (value) {
  this.cloneInvalidEdges = value;
};

/**
 * Function: isCloneInvalidEdges
 *
 * Returns <cloneInvalidEdges> as a boolean.
 */
mxGraph.prototype.isCloneInvalidEdges = function () {
  return this.cloneInvalidEdges;
};

/**
 * Function: setAllowLoops
 *
 * Specifies if loops are allowed.
 *
 * Parameters:
 *
 * value - Boolean indicating if loops are allowed.
 */
mxGraph.prototype.setAllowLoops = function (value) {
  this.allowLoops = value;
};

/**
 * Function: isDisconnectOnMove
 *
 * Returns <disconnectOnMove> as a boolean.
 */
mxGraph.prototype.isDisconnectOnMove = function () {
  return this.disconnectOnMove;
};

/**
 * Function: setDisconnectOnMove
 *
 * Specifies if edges should be disconnected when moved. (Note: Cloned
 * edges are always disconnected.)
 *
 * Parameters:
 *
 * value - Boolean indicating if edges should be disconnected
 * when moved.
 */
mxGraph.prototype.setDisconnectOnMove = function (value) {
  this.disconnectOnMove = value;
};

/**
 * Function: isDropEnabled
 *
 * Returns <dropEnabled> as a boolean.
 */
mxGraph.prototype.isDropEnabled = function () {
  return this.dropEnabled;
};

/**
 * Function: setDropEnabled
 *
 * Specifies if the graph should allow dropping of cells onto or into other
 * cells.
 *
 * Parameters:
 *
 * dropEnabled - Boolean indicating if the graph should allow dropping
 * of cells into other cells.
 */
mxGraph.prototype.setDropEnabled = function (value) {
  this.dropEnabled = value;
};

/**
 * Function: isSplitEnabled
 *
 * Returns <splitEnabled> as a boolean.
 */
mxGraph.prototype.isSplitEnabled = function () {
  return this.splitEnabled;
};

/**
 * Function: setSplitEnabled
 *
 * Specifies if the graph should allow dropping of cells onto or into other
 * cells.
 *
 * Parameters:
 *
 * dropEnabled - Boolean indicating if the graph should allow dropping
 * of cells into other cells.
 */
mxGraph.prototype.setSplitEnabled = function (value) {
  this.splitEnabled = value;
};

/**
 * Function: isCellResizable
 *
 * Returns true if the given cell is resizable. This returns
 * <cellsResizable> for all given cells if <isCellLocked> does not return
 * true for the given cell and its style does not specify
 * <mxConstants.STYLE_RESIZABLE> to be 0.
 *
 * Parameters:
 *
 * cell - <mxCell> whose resizable state should be returned.
 */
mxGraph.prototype.isCellResizable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return (
    this.isCellsResizable() &&
    !this.isCellLocked(cell) &&
    mxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, '1') != '0'
  );
};

/**
 * Function: isCellsResizable
 *
 * Returns <cellsResizable>.
 */
mxGraph.prototype.isCellsResizable = function () {
  return this.cellsResizable;
};

/**
 * Function: setCellsResizable
 *
 * Specifies if the graph should allow resizing of cells. This
 * implementation updates <cellsResizable>.
 *
 * Parameters:
 *
 * value - Boolean indicating if the graph should allow resizing of
 * cells.
 */
mxGraph.prototype.setCellsResizable = function (value) {
  this.cellsResizable = value;
};

/**
 * Function: isTerminalPointMovable
 *
 * Returns true if the given terminal point is movable. This is independent
 * from <isCellConnectable> and <isCellDisconnectable> and controls if terminal
 * points can be moved in the graph if the edge is not connected. Note that it
 * is required for this to return true to connect unconnected edges. This
 * implementation returns true.
 *
 * Parameters:
 *
 * cell - <mxCell> whose terminal point should be moved.
 * source - Boolean indicating if the source or target terminal should be moved.
 */
mxGraph.prototype.isTerminalPointMovable = function (cell, source) {
  return true;
};

/**
 * Function: isCellBendable
 *
 * Returns true if the given cell is bendable. This returns <cellsBendable>
 * for all given cells if <isLocked> does not return true for the given
 * cell and its style does not specify <mxConstants.STYLE_BENDABLE> to be 0.
 *
 * Parameters:
 *
 * cell - <mxCell> whose bendable state should be returned.
 */
mxGraph.prototype.isCellBendable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return this.isCellsBendable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_BENDABLE] != 0;
};

/**
 * Function: isCellsBendable
 *
 * Returns <cellsBenadable>.
 */
mxGraph.prototype.isCellsBendable = function () {
  return this.cellsBendable;
};

/**
 * Function: setCellsBendable
 *
 * Specifies if the graph should allow bending of edges. This
 * implementation updates <bendable>.
 *
 * Parameters:
 *
 * value - Boolean indicating if the graph should allow bending of
 * edges.
 */
mxGraph.prototype.setCellsBendable = function (value) {
  this.cellsBendable = value;
};

/**
 * Function: isCellEditable
 *
 * Returns true if the given cell is editable. This returns <cellsEditable> for
 * all given cells if <isCellLocked> does not return true for the given cell
 * and its style does not specify <mxConstants.STYLE_EDITABLE> to be 0.
 *
 * Parameters:
 *
 * cell - <mxCell> whose editable state should be returned.
 */
mxGraph.prototype.isCellEditable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return this.isCellsEditable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_EDITABLE] != 0;
};

/**
 * Function: isCellsEditable
 *
 * Returns <cellsEditable>.
 */
mxGraph.prototype.isCellsEditable = function () {
  return this.cellsEditable;
};

/**
 * Function: setCellsEditable
 *
 * Specifies if the graph should allow in-place editing for cell labels.
 * This implementation updates <cellsEditable>.
 *
 * Parameters:
 *
 * value - Boolean indicating if the graph should allow in-place
 * editing.
 */
mxGraph.prototype.setCellsEditable = function (value) {
  this.cellsEditable = value;
};

/**
 * Function: isCellDisconnectable
 *
 * Returns true if the given cell is disconnectable from the source or
 * target terminal. This returns <isCellsDisconnectable> for all given
 * cells if <isCellLocked> does not return true for the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> whose disconnectable state should be returned.
 * terminal - <mxCell> that represents the source or target terminal.
 * source - Boolean indicating if the source or target terminal is to be
 * disconnected.
 */
mxGraph.prototype.isCellDisconnectable = function (cell, terminal, source) {
  return this.isCellsDisconnectable() && !this.isCellLocked(cell);
};

/**
 * Function: isCellsDisconnectable
 *
 * Returns <cellsDisconnectable>.
 */
mxGraph.prototype.isCellsDisconnectable = function () {
  return this.cellsDisconnectable;
};

/**
 * Function: setCellsDisconnectable
 *
 * Sets <cellsDisconnectable>.
 */
mxGraph.prototype.setCellsDisconnectable = function (value) {
  this.cellsDisconnectable = value;
};

/**
 * Function: isValidSource
 *
 * Returns true if the given cell is a valid source for new connections.
 * This implementation returns true for all non-null values and is
 * called by is called by <isValidConnection>.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents a possible source or null.
 */
mxGraph.prototype.isValidSource = function (cell) {
  return (
    (cell == null && this.allowDanglingEdges) ||
    (cell != null && (!this.model.isEdge(cell) || this.connectableEdges) && this.isCellConnectable(cell))
  );
};

/**
 * Function: isValidTarget
 *
 * Returns <isValidSource> for the given cell. This is called by
 * <isValidConnection>.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents a possible target or null.
 */
mxGraph.prototype.isValidTarget = function (cell) {
  return this.isValidSource(cell);
};

/**
 * Function: isValidConnection
 *
 * Returns true if the given target cell is a valid target for source.
 * This is a boolean implementation for not allowing connections between
 * certain pairs of vertices and is called by <getEdgeValidationError>.
 * This implementation returns true if <isValidSource> returns true for
 * the source and <isValidTarget> returns true for the target.
 *
 * Parameters:
 *
 * source - <mxCell> that represents the source cell.
 * target - <mxCell> that represents the target cell.
 */
mxGraph.prototype.isValidConnection = function (source, target) {
  return this.isValidSource(source) && this.isValidTarget(target);
};

/**
 * Function: setConnectable
 *
 * Specifies if the graph should allow new connections. This implementation
 * updates <mxConnectionHandler.enabled> in <connectionHandler>.
 *
 * Parameters:
 *
 * connectable - Boolean indicating if new connections should be allowed.
 */
mxGraph.prototype.setConnectable = function (connectable) {
  this.connectionHandler.setEnabled(connectable);
};

/**
 * Function: isConnectable
 *
 * Returns true if the <connectionHandler> is enabled.
 */
mxGraph.prototype.isConnectable = function () {
  return this.connectionHandler.isEnabled();
};

/**
 * Function: setTooltips
 *
 * Specifies if tooltips should be enabled. This implementation updates
 * <mxTooltipHandler.enabled> in <tooltipHandler>.
 *
 * Parameters:
 *
 * enabled - Boolean indicating if tooltips should be enabled.
 */
mxGraph.prototype.setTooltips = function (enabled) {
  this.tooltipHandler.setEnabled(enabled);
};

/**
 * Function: setPanning
 *
 * Specifies if panning should be enabled. This implementation updates
 * <mxPanningHandler.panningEnabled> in <panningHandler>.
 *
 * Parameters:
 *
 * enabled - Boolean indicating if panning should be enabled.
 */
mxGraph.prototype.setPanning = function (enabled) {
  this.panningHandler.panningEnabled = enabled;
};

/**
 * Function: isEditing
 *
 * Returns true if the given cell is currently being edited.
 * If no cell is specified then this returns true if any
 * cell is currently being edited.
 *
 * Parameters:
 *
 * cell - <mxCell> that should be checked.
 */
mxGraph.prototype.isEditing = function (cell) {
  if (this.cellEditor != null) {
    var editingCell = this.cellEditor.getEditingCell();

    return cell == null ? editingCell != null : cell == editingCell;
  }

  return false;
};

/**
 * Function: isAutoSizeCell
 *
 * Returns true if the size of the given cell should automatically be
 * updated after a change of the label. This implementation returns
 * <autoSizeCells> or checks if the cell style does specify
 * <mxConstants.STYLE_AUTOSIZE> to be 1.
 *
 * Parameters:
 *
 * cell - <mxCell> that should be resized.
 */
mxGraph.prototype.isAutoSizeCell = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return this.isAutoSizeCells() || style[mxConstants.STYLE_AUTOSIZE] == 1;
};

/**
 * Function: isAutoSizeCells
 *
 * Returns <autoSizeCells>.
 */
mxGraph.prototype.isAutoSizeCells = function () {
  return this.autoSizeCells;
};

/**
 * Function: setAutoSizeCells
 *
 * Specifies if cell sizes should be automatically updated after a label
 * change. This implementation sets <autoSizeCells> to the given parameter.
 * To update the size of cells when the cells are added, set
 * <autoSizeCellsOnAdd> to true.
 *
 * Parameters:
 *
 * value - Boolean indicating if cells should be resized
 * automatically.
 */
mxGraph.prototype.setAutoSizeCells = function (value) {
  this.autoSizeCells = value;
};

/**
 * Function: isExtendParent
 *
 * Returns true if the parent of the given cell should be extended if the
 * child has been resized so that it overlaps the parent. This
 * implementation returns <isExtendParents> if the cell is not an edge.
 *
 * Parameters:
 *
 * cell - <mxCell> that has been resized.
 */
mxGraph.prototype.isExtendParent = function (cell) {
  return !this.getModel().isEdge(cell) && this.isExtendParents();
};

/**
 * Function: isExtendParents
 *
 * Returns <extendParents>.
 */
mxGraph.prototype.isExtendParents = function () {
  return this.extendParents;
};

/**
 * Function: setExtendParents
 *
 * Sets <extendParents>.
 *
 * Parameters:
 *
 * value - New boolean value for <extendParents>.
 */
mxGraph.prototype.setExtendParents = function (value) {
  this.extendParents = value;
};

/**
 * Function: isExtendParentsOnAdd
 *
 * Returns <extendParentsOnAdd>.
 */
mxGraph.prototype.isExtendParentsOnAdd = function (cell) {
  return this.extendParentsOnAdd;
};

/**
 * Function: setExtendParentsOnAdd
 *
 * Sets <extendParentsOnAdd>.
 *
 * Parameters:
 *
 * value - New boolean value for <extendParentsOnAdd>.
 */
mxGraph.prototype.setExtendParentsOnAdd = function (value) {
  this.extendParentsOnAdd = value;
};

/**
 * Function: isExtendParentsOnMove
 *
 * Returns <extendParentsOnMove>.
 */
mxGraph.prototype.isExtendParentsOnMove = function () {
  return this.extendParentsOnMove;
};

/**
 * Function: setExtendParentsOnMove
 *
 * Sets <extendParentsOnMove>.
 *
 * Parameters:
 *
 * value - New boolean value for <extendParentsOnAdd>.
 */
mxGraph.prototype.setExtendParentsOnMove = function (value) {
  this.extendParentsOnMove = value;
};

/**
 * Function: isRecursiveResize
 *
 * Returns <recursiveResize>.
 *
 * Parameters:
 *
 * state - <mxCellState> that is being resized.
 */
mxGraph.prototype.isRecursiveResize = function (state) {
  return this.recursiveResize;
};

/**
 * Function: setRecursiveResize
 *
 * Sets <recursiveResize>.
 *
 * Parameters:
 *
 * value - New boolean value for <recursiveResize>.
 */
mxGraph.prototype.setRecursiveResize = function (value) {
  this.recursiveResize = value;
};

/**
 * Function: isConstrainChild
 *
 * Returns true if the given cell should be kept inside the bounds of its
 * parent according to the rules defined by <getOverlap> and
 * <isAllowOverlapParent>. This implementation returns false for all children
 * of edges and <isConstrainChildren> otherwise.
 *
 * Parameters:
 *
 * cell - <mxCell> that should be constrained.
 */
mxGraph.prototype.isConstrainChild = function (cell) {
  return this.isConstrainChildren() && !this.getModel().isEdge(this.getModel().getParent(cell));
};

/**
 * Function: isConstrainChildren
 *
 * Returns <constrainChildren>.
 */
mxGraph.prototype.isConstrainChildren = function () {
  return this.constrainChildren;
};

/**
 * Function: setConstrainChildren
 *
 * Sets <constrainChildren>.
 */
mxGraph.prototype.setConstrainChildren = function (value) {
  this.constrainChildren = value;
};

/**
 * Function: isConstrainRelativeChildren
 *
 * Returns <constrainRelativeChildren>.
 */
mxGraph.prototype.isConstrainRelativeChildren = function () {
  return this.constrainRelativeChildren;
};

/**
 * Function: setConstrainRelativeChildren
 *
 * Sets <constrainRelativeChildren>.
 */
mxGraph.prototype.setConstrainRelativeChildren = function (value) {
  this.constrainRelativeChildren = value;
};

/**
 * Function: isConstrainChildren
 *
 * Returns <allowNegativeCoordinates>.
 */
mxGraph.prototype.isAllowNegativeCoordinates = function () {
  return this.allowNegativeCoordinates;
};

/**
 * Function: setConstrainChildren
 *
 * Sets <allowNegativeCoordinates>.
 */
mxGraph.prototype.setAllowNegativeCoordinates = function (value) {
  this.allowNegativeCoordinates = value;
};

/**
 * Function: getOverlap
 *
 * Returns a decimal number representing the amount of the width and height
 * of the given cell that is allowed to overlap its parent. A value of 0
 * means all children must stay inside the parent, 1 means the child is
 * allowed to be placed outside of the parent such that it touches one of
 * the parents sides. If <isAllowOverlapParent> returns false for the given
 * cell, then this method returns 0.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the overlap ratio should be returned.
 */
mxGraph.prototype.getOverlap = function (cell) {
  return this.isAllowOverlapParent(cell) ? this.defaultOverlap : 0;
};

/**
 * Function: isAllowOverlapParent
 *
 * Returns true if the given cell is allowed to be placed outside of the
 * parents area.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the child to be checked.
 */
mxGraph.prototype.isAllowOverlapParent = function (cell) {
  return false;
};

/**
 * Function: getFoldableCells
 *
 * Returns the cells which are movable in the given array of cells.
 */
mxGraph.prototype.getFoldableCells = function (cells, collapse) {
  return this.model.filterCells(
    cells,
    mxUtils.bind(this, function (cell) {
      return this.isCellFoldable(cell, collapse);
    })
  );
};

/**
 * Function: isCellFoldable
 *
 * Returns true if the given cell is foldable. This implementation
 * returns true if the cell has at least one child and its style
 * does not specify <mxConstants.STYLE_FOLDABLE> to be 0.
 *
 * Parameters:
 *
 * cell - <mxCell> whose foldable state should be returned.
 */
mxGraph.prototype.isCellFoldable = function (cell, collapse) {
  var style = this.getCurrentCellStyle(cell);

  return this.model.getChildCount(cell) > 0 && style[mxConstants.STYLE_FOLDABLE] != 0;
};

/**
 * Function: isValidDropTarget
 *
 * Returns true if the given cell is a valid drop target for the specified
 * cells. If <splitEnabled> is true then this returns <isSplitTarget> for
 * the given arguments else it returns true if the cell is not collapsed
 * and its child count is greater than 0.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the possible drop target.
 * cells - <mxCells> that should be dropped into the target.
 * evt - Mouseevent that triggered the invocation.
 */
mxGraph.prototype.isValidDropTarget = function (cell, cells, evt) {
  return (
    cell != null &&
    ((this.isSplitEnabled() && this.isSplitTarget(cell, cells, evt)) ||
      (!this.model.isEdge(cell) &&
        (this.isSwimlane(cell) || (this.model.getChildCount(cell) > 0 && !this.isCellCollapsed(cell)))))
  );
};

/**
 * Function: isSplitTarget
 *
 * Returns true if the given edge may be splitted into two edges with the
 * given cell as a new terminal between the two.
 *
 * Parameters:
 *
 * target - <mxCell> that represents the edge to be splitted.
 * cells - <mxCells> that should split the edge.
 * evt - Mouseevent that triggered the invocation.
 */
mxGraph.prototype.isSplitTarget = function (target, cells, evt) {
  if (
    this.model.isEdge(target) &&
    cells != null &&
    cells.length == 1 &&
    this.isCellConnectable(cells[0]) &&
    this.getEdgeValidationError(target, this.model.getTerminal(target, true), cells[0]) == null
  ) {
    var src = this.model.getTerminal(target, true);
    var trg = this.model.getTerminal(target, false);

    return !this.model.isAncestor(cells[0], src) && !this.model.isAncestor(cells[0], trg);
  }

  return false;
};

/**
 * Function: getDropTarget
 *
 * Returns the given cell if it is a drop target for the given cells or the
 * nearest ancestor that may be used as a drop target for the given cells.
 * If the given array contains a swimlane and <swimlaneNesting> is false
 * then this always returns null. If no cell is given, then the bottommost
 * swimlane at the location of the given event is returned.
 *
 * This function should only be used if <isDropEnabled> returns true.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> which are to be dropped onto the target.
 * evt - Mouseevent for the drag and drop.
 * cell - <mxCell> that is under the mousepointer.
 * clone - Optional boolean to indicate of cells will be cloned.
 */
mxGraph.prototype.getDropTarget = function (cells, evt, cell, clone) {
  if (!this.isSwimlaneNesting()) {
    for (var i = 0; i < cells.length; i++) {
      if (this.isSwimlane(cells[i])) {
        return null;
      }
    }
  }

  var pt = mxUtils.convertPoint(this.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  pt.x -= this.panDx;
  pt.y -= this.panDy;
  var swimlane = this.getSwimlaneAt(pt.x, pt.y);

  if (cell == null) {
    cell = swimlane;
  } else if (swimlane != null) {
    // Checks if the cell is an ancestor of the swimlane
    // under the mouse and uses the swimlane in that case
    var tmp = this.model.getParent(swimlane);

    while (tmp != null && this.isSwimlane(tmp) && tmp != cell) {
      tmp = this.model.getParent(tmp);
    }

    if (tmp == cell) {
      cell = swimlane;
    }
  }

  while (cell != null && !this.isValidDropTarget(cell, cells, evt) && !this.model.isLayer(cell)) {
    cell = this.model.getParent(cell);
  }

  // Checks if parent is dropped into child if not cloning
  if (clone == null || !clone) {
    var parent = cell;

    while (parent != null && mxUtils.indexOf(cells, parent) < 0) {
      parent = this.model.getParent(parent);
    }
  }

  return !this.model.isLayer(cell) && parent == null ? cell : null;
};

/**
 * Group: Cell retrieval
 */

/**
 * Function: getDefaultParent
 *
 * Returns <defaultParent> or <mxGraphView.currentRoot> or the first child
 * child of <mxGraphModel.root> if both are null. The value returned by
 * this function should be used as the parent for new cells (aka default
 * layer).
 */
mxGraph.prototype.getDefaultParent = function () {
  var parent = this.getCurrentRoot();

  if (parent == null) {
    parent = this.defaultParent;

    if (parent == null) {
      var root = this.model.getRoot();
      parent = this.model.getChildAt(root, 0);
    }
  }

  return parent;
};

/**
 * Function: setDefaultParent
 *
 * Sets the <defaultParent> to the given cell. Set this to null to return
 * the first child of the root in getDefaultParent.
 */
mxGraph.prototype.setDefaultParent = function (cell) {
  this.defaultParent = cell;
};

/**
 * Function: getSwimlane
 *
 * Returns the nearest ancestor of the given cell which is a swimlane, or
 * the given cell, if it is itself a swimlane.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the ancestor swimlane should be returned.
 */
mxGraph.prototype.getSwimlane = function (cell) {
  while (cell != null && !this.isSwimlane(cell)) {
    cell = this.model.getParent(cell);
  }

  return cell;
};

/**
 * Function: getSwimlaneAt
 *
 * Returns the bottom-most swimlane that intersects the given point (x, y)
 * in the cell hierarchy that starts at the given parent.
 *
 * Parameters:
 *
 * x - X-coordinate of the location to be checked.
 * y - Y-coordinate of the location to be checked.
 * parent - <mxCell> that should be used as the root of the recursion.
 * Default is <defaultParent>.
 */
mxGraph.prototype.getSwimlaneAt = function (x, y, parent) {
  if (parent == null) {
    parent = this.getCurrentRoot();

    if (parent == null) {
      parent = this.model.getRoot();
    }
  }

  if (parent != null) {
    var childCount = this.model.getChildCount(parent);

    for (var i = 0; i < childCount; i++) {
      var child = this.model.getChildAt(parent, i);

      if (child != null) {
        var result = this.getSwimlaneAt(x, y, child);

        if (result != null) {
          return result;
        } else if (this.isCellVisible(child) && this.isSwimlane(child)) {
          var state = this.view.getState(child);

          if (this.intersects(state, x, y)) {
            return child;
          }
        }
      }
    }
  }

  return null;
};

/**
 * Function: getCellAt
 *
 * Returns the bottom-most cell that intersects the given point (x, y) in
 * the cell hierarchy starting at the given parent. This will also return
 * swimlanes if the given location intersects the content area of the
 * swimlane. If this is not desired, then the <hitsSwimlaneContent> may be
 * used if the returned cell is a swimlane to determine if the location
 * is inside the content area or on the actual title of the swimlane.
 *
 * Parameters:
 *
 * x - X-coordinate of the location to be checked.
 * y - Y-coordinate of the location to be checked.
 * parent - <mxCell> that should be used as the root of the recursion.
 * Default is current root of the view or the root of the model.
 * vertices - Optional boolean indicating if vertices should be returned.
 * Default is true.
 * edges - Optional boolean indicating if edges should be returned. Default
 * is true.
 * ignoreFn - Optional function that returns true if cell should be ignored.
 * The function is passed the cell state and the x and y parameter.
 */
mxGraph.prototype.getCellAt = function (x, y, parent, vertices, edges, ignoreFn) {
  vertices = vertices != null ? vertices : true;
  edges = edges != null ? edges : true;

  if (parent == null) {
    parent = this.getCurrentRoot();

    if (parent == null) {
      parent = this.getModel().getRoot();
    }
  }

  if (parent != null) {
    var childCount = this.model.getChildCount(parent);

    for (var i = childCount - 1; i >= 0; i--) {
      var cell = this.model.getChildAt(parent, i);
      var result = this.getCellAt(x, y, cell, vertices, edges, ignoreFn);

      if (result != null) {
        return result;
      } else if (
        this.isCellVisible(cell) &&
        ((edges && this.model.isEdge(cell)) || (vertices && this.model.isVertex(cell)))
      ) {
        var state = this.view.getState(cell);

        if (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) && this.intersects(state, x, y)) {
          return cell;
        }
      }
    }
  }

  return null;
};

/**
 * Function: intersects
 *
 * Returns the bottom-most cell that intersects the given point (x, y) in
 * the cell hierarchy that starts at the given parent.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the cell state.
 * x - X-coordinate of the location to be checked.
 * y - Y-coordinate of the location to be checked.
 */
mxGraph.prototype.intersects = function (state, x, y) {
  if (state != null) {
    var pts = state.absolutePoints;

    if (pts != null) {
      var t2 = this.tolerance * this.tolerance;
      var pt = pts[0];

      for (var i = 1; i < pts.length; i++) {
        var next = pts[i];
        var dist = mxUtils.ptSegDistSq(pt.x, pt.y, next.x, next.y, x, y);

        if (dist <= t2) {
          return true;
        }

        pt = next;
      }
    } else {
      var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);

      if (alpha != 0) {
        var cos = Math.cos(-alpha);
        var sin = Math.sin(-alpha);
        var cx = new mxPoint(state.getCenterX(), state.getCenterY());
        var pt = mxUtils.getRotatedPoint(new mxPoint(x, y), cos, sin, cx);
        x = pt.x;
        y = pt.y;
      }

      if (mxUtils.contains(state, x, y)) {
        return true;
      }
    }
  }

  return false;
};

/**
 * Function: hitsSwimlaneContent
 *
 * Returns true if the given coordinate pair is inside the content
 * are of the given swimlane.
 *
 * Parameters:
 *
 * swimlane - <mxCell> that specifies the swimlane.
 * x - X-coordinate of the mouse event.
 * y - Y-coordinate of the mouse event.
 */
mxGraph.prototype.hitsSwimlaneContent = function (swimlane, x, y) {
  var state = this.getView().getState(swimlane);
  var size = this.getStartSize(swimlane);

  if (state != null) {
    var scale = this.getView().getScale();
    x -= state.x;
    y -= state.y;

    if (size.width > 0 && x > 0 && x > size.width * scale) {
      return true;
    } else if (size.height > 0 && y > 0 && y > size.height * scale) {
      return true;
    }
  }

  return false;
};

/**
 * Function: getChildVertices
 *
 * Returns the visible child vertices of the given parent.
 *
 * Parameters:
 *
 * parent - <mxCell> whose children should be returned.
 */
mxGraph.prototype.getChildVertices = function (parent) {
  return this.getChildCells(parent, true, false);
};

/**
 * Function: getChildEdges
 *
 * Returns the visible child edges of the given parent.
 *
 * Parameters:
 *
 * parent - <mxCell> whose child vertices should be returned.
 */
mxGraph.prototype.getChildEdges = function (parent) {
  return this.getChildCells(parent, false, true);
};

/**
 * Function: getChildCells
 *
 * Returns the visible child vertices or edges in the given parent. If
 * vertices and edges is false, then all children are returned.
 *
 * Parameters:
 *
 * parent - <mxCell> whose children should be returned.
 * vertices - Optional boolean that specifies if child vertices should
 * be returned. Default is false.
 * edges - Optional boolean that specifies if child edges should
 * be returned. Default is false.
 */
mxGraph.prototype.getChildCells = function (parent, vertices, edges) {
  parent = parent != null ? parent : this.getDefaultParent();
  vertices = vertices != null ? vertices : false;
  edges = edges != null ? edges : false;

  var cells = this.model.getChildCells(parent, vertices, edges);
  var result = [];

  // Filters out the non-visible child cells
  for (var i = 0; i < cells.length; i++) {
    if (this.isCellVisible(cells[i])) {
      result.push(cells[i]);
    }
  }

  return result;
};

/**
 * Function: getConnections
 *
 * Returns all visible edges connected to the given cell without loops.
 *
 * Parameters:
 *
 * cell - <mxCell> whose connections should be returned.
 * parent - Optional parent of the opposite end for a connection to be
 * returned.
 */
mxGraph.prototype.getConnections = function (cell, parent) {
  return this.getEdges(cell, parent, true, true, false);
};

/**
 * Function: getIncomingEdges
 *
 * Returns the visible incoming edges for the given cell. If the optional
 * parent argument is specified, then only child edges of the given parent
 * are returned.
 *
 * Parameters:
 *
 * cell - <mxCell> whose incoming edges should be returned.
 * parent - Optional parent of the opposite end for an edge to be
 * returned.
 */
mxGraph.prototype.getIncomingEdges = function (cell, parent) {
  return this.getEdges(cell, parent, true, false, false);
};

/**
 * Function: getOutgoingEdges
 *
 * Returns the visible outgoing edges for the given cell. If the optional
 * parent argument is specified, then only child edges of the given parent
 * are returned.
 *
 * Parameters:
 *
 * cell - <mxCell> whose outgoing edges should be returned.
 * parent - Optional parent of the opposite end for an edge to be
 * returned.
 */
mxGraph.prototype.getOutgoingEdges = function (cell, parent) {
  return this.getEdges(cell, parent, false, true, false);
};

/**
 * Function: getEdges
 *
 * Returns the incoming and/or outgoing edges for the given cell.
 * If the optional parent argument is specified, then only edges are returned
 * where the opposite is in the given parent cell. If at least one of incoming
 * or outgoing is true, then loops are ignored, if both are false, then all
 * edges connected to the given cell are returned including loops.
 *
 * Parameters:
 *
 * cell - <mxCell> whose edges should be returned.
 * parent - Optional parent of the opposite end for an edge to be
 * returned.
 * incoming - Optional boolean that specifies if incoming edges should
 * be included in the result. Default is true.
 * outgoing - Optional boolean that specifies if outgoing edges should
 * be included in the result. Default is true.
 * includeLoops - Optional boolean that specifies if loops should be
 * included in the result. Default is true.
 * recurse - Optional boolean the specifies if the parent specified only
 * need be an ancestral parent, true, or the direct parent, false.
 * Default is false
 */
mxGraph.prototype.getEdges = function (cell, parent, incoming, outgoing, includeLoops, recurse) {
  incoming = incoming != null ? incoming : true;
  outgoing = outgoing != null ? outgoing : true;
  includeLoops = includeLoops != null ? includeLoops : true;
  recurse = recurse != null ? recurse : false;

  var edges = [];
  var isCollapsed = this.isCellCollapsed(cell);
  var childCount = this.model.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    var child = this.model.getChildAt(cell, i);

    if (isCollapsed || !this.isCellVisible(child)) {
      edges = edges.concat(this.model.getEdges(child, incoming, outgoing));
    }
  }

  edges = edges.concat(this.model.getEdges(cell, incoming, outgoing));
  var result = [];

  for (var i = 0; i < edges.length; i++) {
    var state = this.view.getState(edges[i]);

    var source = state != null ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
    var target = state != null ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);

    if (
      (includeLoops && source == target) ||
      (source != target &&
        ((incoming && target == cell && (parent == null || this.isValidAncestor(source, parent, recurse))) ||
          (outgoing && source == cell && (parent == null || this.isValidAncestor(target, parent, recurse)))))
    ) {
      result.push(edges[i]);
    }
  }

  return result;
};

/**
 * Function: isValidAncestor
 *
 * Returns whether or not the specified parent is a valid
 * ancestor of the specified cell, either direct or indirectly
 * based on whether ancestor recursion is enabled.
 *
 * Parameters:
 *
 * cell - <mxCell> the possible child cell
 * parent - <mxCell> the possible parent cell
 * recurse - boolean whether or not to recurse the child ancestors
 */
mxGraph.prototype.isValidAncestor = function (cell, parent, recurse) {
  return recurse ? this.model.isAncestor(parent, cell) : this.model.getParent(cell) == parent;
};

/**
 * Function: getOpposites
 *
 * Returns all distinct visible opposite cells for the specified terminal
 * on the given edges.
 *
 * Parameters:
 *
 * edges - Array of <mxCells> that contains the edges whose opposite
 * terminals should be returned.
 * terminal - Terminal that specifies the end whose opposite should be
 * returned.
 * sources - Optional boolean that specifies if source terminals should be
 * included in the result. Default is true.
 * targets - Optional boolean that specifies if targer terminals should be
 * included in the result. Default is true.
 */
mxGraph.prototype.getOpposites = function (edges, terminal, sources, targets) {
  sources = sources != null ? sources : true;
  targets = targets != null ? targets : true;

  var terminals = [];

  // Fast lookup to avoid duplicates in terminals array
  var dict = new mxDictionary();

  if (edges != null) {
    for (var i = 0; i < edges.length; i++) {
      var state = this.view.getState(edges[i]);

      var source = state != null ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
      var target = state != null ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);

      // Checks if the terminal is the source of the edge and if the
      // target should be stored in the result
      if (source == terminal && target != null && target != terminal && targets) {
        if (!dict.get(target)) {
          dict.put(target, true);
          terminals.push(target);
        }
      }

      // Checks if the terminal is the taget of the edge and if the
      // source should be stored in the result
      else if (target == terminal && source != null && source != terminal && sources) {
        if (!dict.get(source)) {
          dict.put(source, true);
          terminals.push(source);
        }
      }
    }
  }

  return terminals;
};

/**
 * Function: getEdgesBetween
 *
 * Returns the edges between the given source and target. This takes into
 * account collapsed and invisible cells and returns the connected edges
 * as displayed on the screen.
 *
 * Parameters:
 *
 * source -
 * target -
 * directed -
 */
mxGraph.prototype.getEdgesBetween = function (source, target, directed) {
  directed = directed != null ? directed : false;
  var edges = this.getEdges(source);
  var result = [];

  // Checks if the edge is connected to the correct
  // cell and returns the first match
  for (var i = 0; i < edges.length; i++) {
    var state = this.view.getState(edges[i]);

    var src = state != null ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
    var trg = state != null ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);

    if ((src == source && trg == target) || (!directed && src == target && trg == source)) {
      result.push(edges[i]);
    }
  }

  return result;
};

/**
 * Function: getPointForEvent
 *
 * Returns an <mxPoint> representing the given event in the unscaled,
 * non-translated coordinate space of <container> and applies the grid.
 *
 * Parameters:
 *
 * evt - Mousevent that contains the mouse pointer location.
 * addOffset - Optional boolean that specifies if the position should be
 * offset by half of the <gridSize>. Default is true.
 */
mxGraph.prototype.getPointForEvent = function (evt, addOffset) {
  var p = mxUtils.convertPoint(this.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));

  var s = this.view.scale;
  var tr = this.view.translate;
  var off = addOffset != false ? this.gridSize / 2 : 0;

  p.x = this.snap(p.x / s - tr.x - off);
  p.y = this.snap(p.y / s - tr.y - off);

  return p;
};

/**
 * Function: getCells
 *
 * Returns the child vertices and edges of the given parent that are contained
 * in the given rectangle. The result is added to the optional result array,
 * which is returned. If no result array is specified then a new array is
 * created and returned.
 *
 * Parameters:
 *
 * x - X-coordinate of the rectangle.
 * y - Y-coordinate of the rectangle.
 * width - Width of the rectangle.
 * height - Height of the rectangle.
 * parent - <mxCell> that should be used as the root of the recursion.
 * Default is current root of the view or the root of the model.
 * result - Optional array to store the result in.
 * intersection - Optional <mxRectangle> to check vertices for intersection.
 * ignoreFn - Optional function to check if a cell state is ignored.
 * includeDescendants - Optional boolean flag to add descendants to the result.
 * Default is false.
 */
mxGraph.prototype.getCells = function (
  x,
  y,
  width,
  height,
  parent,
  result,
  intersection,
  ignoreFn,
  includeDescendants
) {
  result = result != null ? result : [];

  if (width > 0 || height > 0 || intersection != null) {
    var model = this.getModel();
    var right = x + width;
    var bottom = y + height;

    if (parent == null) {
      parent = this.getCurrentRoot();

      if (parent == null) {
        parent = model.getRoot();
      }
    }

    if (parent != null) {
      var childCount = model.getChildCount(parent);

      for (var i = 0; i < childCount; i++) {
        var cell = model.getChildAt(parent, i);
        var state = this.view.getState(cell);

        if (state != null && this.isCellVisible(cell) && (ignoreFn == null || !ignoreFn(state))) {
          var deg = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0;
          var box = state;

          if (deg != 0) {
            box = mxUtils.getBoundingBox(box, deg);
          }

          var hit =
            (intersection != null && model.isVertex(cell) && mxUtils.intersects(intersection, box)) ||
            (intersection == null &&
              (model.isEdge(cell) || model.isVertex(cell)) &&
              box.x >= x &&
              box.y + box.height <= bottom &&
              box.y >= y &&
              box.x + box.width <= right);

          if (hit) {
            result.push(cell);
          }

          if (!hit || includeDescendants) {
            this.getCells(x, y, width, height, cell, result, intersection, ignoreFn, includeDescendants);
          }
        }
      }
    }
  }

  return result;
};

/**
 * Function: getCellsBeyond
 *
 * Returns the children of the given parent that are contained in the
 * halfpane from the given point (x0, y0) rightwards or downwards
 * depending on rightHalfpane and bottomHalfpane.
 *
 * Parameters:
 *
 * x0 - X-coordinate of the origin.
 * y0 - Y-coordinate of the origin.
 * parent - Optional <mxCell> whose children should be checked. Default is
 * <defaultParent>.
 * rightHalfpane - Boolean indicating if the cells in the right halfpane
 * from the origin should be returned.
 * bottomHalfpane - Boolean indicating if the cells in the bottom halfpane
 * from the origin should be returned.
 */
mxGraph.prototype.getCellsBeyond = function (x0, y0, parent, rightHalfpane, bottomHalfpane) {
  var result = [];

  if (rightHalfpane || bottomHalfpane) {
    if (parent == null) {
      parent = this.getDefaultParent();
    }

    if (parent != null) {
      var childCount = this.model.getChildCount(parent);

      for (var i = 0; i < childCount; i++) {
        var child = this.model.getChildAt(parent, i);
        var state = this.view.getState(child);

        if (this.isCellVisible(child) && state != null) {
          if ((!rightHalfpane || state.x >= x0) && (!bottomHalfpane || state.y >= y0)) {
            result.push(child);
          }
        }
      }
    }
  }

  return result;
};

/**
 * Function: findTreeRoots
 *
 * Returns all children in the given parent which do not have incoming
 * edges. If the result is empty then the with the greatest difference
 * between incoming and outgoing edges is returned.
 *
 * Parameters:
 *
 * parent - <mxCell> whose children should be checked.
 * isolate - Optional boolean that specifies if edges should be ignored if
 * the opposite end is not a child of the given parent cell. Default is
 * false.
 * invert - Optional boolean that specifies if outgoing or incoming edges
 * should be counted for a tree root. If false then outgoing edges will be
 * counted. Default is false.
 */
mxGraph.prototype.findTreeRoots = function (parent, isolate, invert) {
  isolate = isolate != null ? isolate : false;
  invert = invert != null ? invert : false;
  var roots = [];

  if (parent != null) {
    var model = this.getModel();
    var childCount = model.getChildCount(parent);
    var best = null;
    var maxDiff = 0;

    for (var i = 0; i < childCount; i++) {
      var cell = model.getChildAt(parent, i);

      if (this.model.isVertex(cell) && this.isCellVisible(cell)) {
        var conns = this.getConnections(cell, isolate ? parent : null);
        var fanOut = 0;
        var fanIn = 0;

        for (var j = 0; j < conns.length; j++) {
          var src = this.view.getVisibleTerminal(conns[j], true);

          if (src == cell) {
            fanOut++;
          } else {
            fanIn++;
          }
        }

        if ((invert && fanOut == 0 && fanIn > 0) || (!invert && fanIn == 0 && fanOut > 0)) {
          roots.push(cell);
        }

        var diff = invert ? fanIn - fanOut : fanOut - fanIn;

        if (diff > maxDiff) {
          maxDiff = diff;
          best = cell;
        }
      }
    }

    if (roots.length == 0 && best != null) {
      roots.push(best);
    }
  }

  return roots;
};

/**
 * Function: traverse
 *
 * Traverses the (directed) graph invoking the given function for each
 * visited vertex and edge. The function is invoked with the current vertex
 * and the incoming edge as a parameter. This implementation makes sure
 * each vertex is only visited once. The function may return false if the
 * traversal should stop at the given vertex.
 *
 * Example:
 *
 * (code)
 * mxLog.show();
 * var cell = graph.getSelectionCell();
 * graph.traverse(cell, false, function(vertex, edge)
 * {
 *   mxLog.debug(graph.getLabel(vertex));
 * });
 * (end)
 *
 * Parameters:
 *
 * vertex - <mxCell> that represents the vertex where the traversal starts.
 * directed - Optional boolean indicating if edges should only be traversed
 * from source to target. Default is true.
 * func - Visitor function that takes the current vertex and the incoming
 * edge as arguments. The traversal stops if the function returns false.
 * edge - Optional <mxCell> that represents the incoming edge. This is
 * null for the first step of the traversal.
 * visited - Optional <mxDictionary> from cells to true for the visited cells.
 * inverse - Optional boolean to traverse in inverse direction. Default is false.
 * This is ignored if directed is false.
 */
mxGraph.prototype.traverse = function (vertex, directed, func, edge, visited, inverse) {
  if (func != null && vertex != null) {
    directed = directed != null ? directed : true;
    inverse = inverse != null ? inverse : false;
    visited = visited || new mxDictionary();

    if (!visited.get(vertex)) {
      visited.put(vertex, true);
      var result = func(vertex, edge);

      if (result == null || result) {
        var edgeCount = this.model.getEdgeCount(vertex);

        if (edgeCount > 0) {
          for (var i = 0; i < edgeCount; i++) {
            var e = this.model.getEdgeAt(vertex, i);
            var isSource = this.model.getTerminal(e, true) == vertex;

            if (!directed || !inverse == isSource) {
              var next = this.model.getTerminal(e, !isSource);
              this.traverse(next, directed, func, e, visited, inverse);
            }
          }
        }
      }
    }
  }
};

/**
 * Group: Selection
 */

/**
 * Function: isCellSelected
 *
 * Returns true if the given cell is selected.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the selection state should be returned.
 */
mxGraph.prototype.isCellSelected = function (cell) {
  return this.getSelectionModel().isSelected(cell);
};

/**
 * Function: isSelectionEmpty
 *
 * Returns true if the selection is empty.
 */
mxGraph.prototype.isSelectionEmpty = function () {
  return this.getSelectionModel().isEmpty();
};

/**
 * Function: clearSelection
 *
 * Clears the selection using <mxGraphSelectionModel.clear>.
 */
mxGraph.prototype.clearSelection = function () {
  return this.getSelectionModel().clear();
};

/**
 * Function: getSelectionCount
 *
 * Returns the number of selected cells.
 */
mxGraph.prototype.getSelectionCount = function () {
  return this.getSelectionModel().cells.length;
};

/**
 * Function: getSelectionCell
 *
 * Returns the first cell from the array of selected <mxCells>.
 */
mxGraph.prototype.getSelectionCell = function () {
  return this.getSelectionModel().cells[0];
};

/**
 * Function: getSelectionCells
 *
 * Returns the array of selected <mxCells>.
 */
mxGraph.prototype.getSelectionCells = function () {
  return this.getSelectionModel().cells.slice();
};

/**
 * Function: setSelectionCell
 *
 * Sets the selection cell.
 *
 * Parameters:
 *
 * cell - <mxCell> to be selected.
 */
mxGraph.prototype.setSelectionCell = function (cell) {
  this.getSelectionModel().setCell(cell);
};

/**
 * Function: setSelectionCells
 *
 * Sets the selection cell.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be selected.
 */
mxGraph.prototype.setSelectionCells = function (cells) {
  this.getSelectionModel().setCells(cells);
};

/**
 * Function: addSelectionCell
 *
 * Adds the given cell to the selection.
 *
 * Parameters:
 *
 * cell - <mxCell> to be add to the selection.
 */
mxGraph.prototype.addSelectionCell = function (cell) {
  this.getSelectionModel().addCell(cell);
};

/**
 * Function: addSelectionCells
 *
 * Adds the given cells to the selection.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be added to the selection.
 */
mxGraph.prototype.addSelectionCells = function (cells) {
  this.getSelectionModel().addCells(cells);
};

/**
 * Function: removeSelectionCell
 *
 * Removes the given cell from the selection.
 *
 * Parameters:
 *
 * cell - <mxCell> to be removed from the selection.
 */
mxGraph.prototype.removeSelectionCell = function (cell) {
  this.getSelectionModel().removeCell(cell);
};

/**
 * Function: removeSelectionCells
 *
 * Removes the given cells from the selection.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be removed from the selection.
 */
mxGraph.prototype.removeSelectionCells = function (cells) {
  this.getSelectionModel().removeCells(cells);
};

/**
 * Function: selectRegion
 *
 * Selects and returns the cells inside the given rectangle for the
 * specified event.
 *
 * Parameters:
 *
 * rect - <mxRectangle> that represents the region to be selected.
 * evt - Mouseevent that triggered the selection.
 */
mxGraph.prototype.selectRegion = function (rect, evt) {
  var cells = this.getCells(rect.x, rect.y, rect.width, rect.height);
  this.selectCellsForEvent(cells, evt);

  return cells;
};

/**
 * Function: selectNextCell
 *
 * Selects the next cell.
 */
mxGraph.prototype.selectNextCell = function () {
  this.selectCell(true);
};

/**
 * Function: selectPreviousCell
 *
 * Selects the previous cell.
 */
mxGraph.prototype.selectPreviousCell = function () {
  this.selectCell();
};

/**
 * Function: selectParentCell
 *
 * Selects the parent cell.
 */
mxGraph.prototype.selectParentCell = function () {
  this.selectCell(false, true);
};

/**
 * Function: selectChildCell
 *
 * Selects the first child cell.
 */
mxGraph.prototype.selectChildCell = function () {
  this.selectCell(false, false, true);
};

/**
 * Function: selectCell
 *
 * Selects the next, parent, first child or previous cell, if all arguments
 * are false.
 *
 * Parameters:
 *
 * isNext - Boolean indicating if the next cell should be selected.
 * isParent - Boolean indicating if the parent cell should be selected.
 * isChild - Boolean indicating if the first child cell should be selected.
 */
mxGraph.prototype.selectCell = function (isNext, isParent, isChild) {
  var sel = this.selectionModel;
  var cell = sel.cells.length > 0 ? sel.cells[0] : null;

  if (sel.cells.length > 1) {
    sel.clear();
  }

  var parent = cell != null ? this.model.getParent(cell) : this.getDefaultParent();

  var childCount = this.model.getChildCount(parent);

  if (cell == null && childCount > 0) {
    var child = this.model.getChildAt(parent, 0);
    this.setSelectionCell(child);
  } else if (
    (cell == null || isParent) &&
    this.view.getState(parent) != null &&
    this.model.getGeometry(parent) != null
  ) {
    if (this.getCurrentRoot() != parent) {
      this.setSelectionCell(parent);
    }
  } else if (cell != null && isChild) {
    var tmp = this.model.getChildCount(cell);

    if (tmp > 0) {
      var child = this.model.getChildAt(cell, 0);
      this.setSelectionCell(child);
    }
  } else if (childCount > 0) {
    var i = parent.getIndex(cell);

    if (isNext) {
      i++;
      var child = this.model.getChildAt(parent, i % childCount);
      this.setSelectionCell(child);
    } else {
      i--;
      var index = i < 0 ? childCount - 1 : i;
      var child = this.model.getChildAt(parent, index);
      this.setSelectionCell(child);
    }
  }
};

/**
 * Function: selectAll
 *
 * Selects all children of the given parent cell or the children of the
 * default parent if no parent is specified. To select leaf vertices and/or
 * edges use <selectCells>.
 *
 * Parameters:
 *
 * parent - Optional <mxCell> whose children should be selected.
 * Default is <defaultParent>.
 * descendants - Optional boolean specifying whether all descendants should be
 * selected. Default is false.
 */
mxGraph.prototype.selectAll = function (parent, descendants) {
  parent = parent || this.getDefaultParent();

  var cells = descendants
    ? this.model.filterDescendants(
        mxUtils.bind(this, function (cell) {
          return cell != parent && this.view.getState(cell) != null;
        }),
        parent
      )
    : this.model.getChildren(parent);

  if (cells != null) {
    this.setSelectionCells(cells);
  }
};

/**
 * Function: selectVertices
 *
 * Select all vertices inside the given parent or the default parent.
 */
mxGraph.prototype.selectVertices = function (parent, selectGroups) {
  this.selectCells(true, false, parent, selectGroups);
};

/**
 * Function: selectVertices
 *
 * Select all vertices inside the given parent or the default parent.
 */
mxGraph.prototype.selectEdges = function (parent) {
  this.selectCells(false, true, parent);
};

/**
 * Function: selectCells
 *
 * Selects all vertices and/or edges depending on the given boolean
 * arguments recursively, starting at the given parent or the default
 * parent if no parent is specified. Use <selectAll> to select all cells.
 * For vertices, only cells with no children are selected.
 *
 * Parameters:
 *
 * vertices - Boolean indicating if vertices should be selected.
 * edges - Boolean indicating if edges should be selected.
 * parent - Optional <mxCell> that acts as the root of the recursion.
 * Default is <defaultParent>.
 * selectGroups - Optional boolean that specifies if groups should be
 * selected. Default is false.
 */
mxGraph.prototype.selectCells = function (vertices, edges, parent, selectGroups) {
  parent = parent || this.getDefaultParent();

  var filter = mxUtils.bind(this, function (cell) {
    return (
      this.view.getState(cell) != null &&
      (((selectGroups || this.model.getChildCount(cell) == 0) &&
        this.model.isVertex(cell) &&
        vertices &&
        !this.model.isEdge(this.model.getParent(cell))) ||
        (this.model.isEdge(cell) && edges))
    );
  });

  var cells = this.model.filterDescendants(filter, parent);

  if (cells != null) {
    this.setSelectionCells(cells);
  }
};

/**
 * Function: selectCellForEvent
 *
 * Selects the given cell by either adding it to the selection or
 * replacing the selection depending on whether the given mouse event is a
 * toggle event.
 *
 * Parameters:
 *
 * cell - <mxCell> to be selected.
 * evt - Optional mouseevent that triggered the selection.
 */
mxGraph.prototype.selectCellForEvent = function (cell, evt) {
  var isSelected = this.isCellSelected(cell);

  if (this.isToggleEvent(evt)) {
    if (isSelected) {
      this.removeSelectionCell(cell);
    } else {
      this.addSelectionCell(cell);
    }
  } else if (!isSelected || this.getSelectionCount() != 1) {
    this.setSelectionCell(cell);
  }
};

/**
 * Function: selectCellsForEvent
 *
 * Selects the given cells by either adding them to the selection or
 * replacing the selection depending on whether the given mouse event is a
 * toggle event.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> to be selected.
 * evt - Optional mouseevent that triggered the selection.
 */
mxGraph.prototype.selectCellsForEvent = function (cells, evt) {
  if (this.isToggleEvent(evt)) {
    this.addSelectionCells(cells);
  } else {
    this.setSelectionCells(cells);
  }
};

/**
 * Group: Selection state
 */

/**
 * Function: createHandler
 *
 * Creates a new handler for the given cell state. This implementation
 * returns a new <mxEdgeHandler> of the corresponding cell is an edge,
 * otherwise it returns an <mxVertexHandler>.
 *
 * Parameters:
 *
 * state - <mxCellState> whose handler should be created.
 */
mxGraph.prototype.createHandler = function (state) {
  var result = null;

  if (state != null) {
    if (this.model.isEdge(state.cell)) {
      var source = state.getVisibleTerminalState(true);
      var target = state.getVisibleTerminalState(false);
      var geo = this.getCellGeometry(state.cell);

      var edgeStyle = this.view.getEdgeStyle(state, geo != null ? geo.points : null, source, target);
      result = this.createEdgeHandler(state, edgeStyle);
    } else {
      result = this.createVertexHandler(state);
    }
  }

  return result;
};

/**
 * Function: createVertexHandler
 *
 * Hooks to create a new <mxVertexHandler> for the given <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> to create the handler for.
 */
mxGraph.prototype.createVertexHandler = function (state) {
  return new mxVertexHandler(state);
};

/**
 * Function: createEdgeHandler
 *
 * Hooks to create a new <mxEdgeHandler> for the given <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> to create the handler for.
 */
mxGraph.prototype.createEdgeHandler = function (state, edgeStyle) {
  var result = null;

  if (
    edgeStyle == mxEdgeStyle.Loop ||
    edgeStyle == mxEdgeStyle.ElbowConnector ||
    edgeStyle == mxEdgeStyle.SideToSide ||
    edgeStyle == mxEdgeStyle.TopToBottom
  ) {
    result = this.createElbowEdgeHandler(state);
  } else if (edgeStyle == mxEdgeStyle.SegmentConnector || edgeStyle == mxEdgeStyle.OrthConnector) {
    result = this.createEdgeSegmentHandler(state);
  } else {
    result = new mxEdgeHandler(state);
  }

  return result;
};

/**
 * Function: createEdgeSegmentHandler
 *
 * Hooks to create a new <mxEdgeSegmentHandler> for the given <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> to create the handler for.
 */
mxGraph.prototype.createEdgeSegmentHandler = function (state) {
  return new mxEdgeSegmentHandler(state);
};

/**
 * Function: createElbowEdgeHandler
 *
 * Hooks to create a new <mxElbowEdgeHandler> for the given <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> to create the handler for.
 */
mxGraph.prototype.createElbowEdgeHandler = function (state) {
  return new mxElbowEdgeHandler(state);
};

/**
 * Group: Graph events
 */

/**
 * Function: addMouseListener
 *
 * Adds a listener to the graph event dispatch loop. The listener
 * must implement the mouseDown, mouseMove and mouseUp methods
 * as shown in the <mxMouseEvent> class.
 *
 * Parameters:
 *
 * listener - Listener to be added to the graph event listeners.
 */
mxGraph.prototype.addMouseListener = function (listener) {
  if (this.mouseListeners == null) {
    this.mouseListeners = [];
  }

  this.mouseListeners.push(listener);
};

/**
 * Function: removeMouseListener
 *
 * Removes the specified graph listener.
 *
 * Parameters:
 *
 * listener - Listener to be removed from the graph event listeners.
 */
mxGraph.prototype.removeMouseListener = function (listener) {
  if (this.mouseListeners != null) {
    for (var i = 0; i < this.mouseListeners.length; i++) {
      if (this.mouseListeners[i] == listener) {
        this.mouseListeners.splice(i, 1);
        break;
      }
    }
  }
};

/**
 * Function: updateMouseEvent
 *
 * Sets the graphX and graphY properties if the given <mxMouseEvent> if
 * required and returned the event.
 *
 * Parameters:
 *
 * me - <mxMouseEvent> to be updated.
 * evtName - Name of the mouse event.
 */
mxGraph.prototype.updateMouseEvent = function (me, evtName) {
  if (me.graphX == null || me.graphY == null) {
    var pt = mxUtils.convertPoint(this.container, me.getX(), me.getY());

    me.graphX = pt.x - this.panDx;
    me.graphY = pt.y - this.panDy;

    // Searches for rectangles using method if native hit detection is disabled on shape
    if (me.getCell() == null && this.isMouseDown && evtName == mxEvent.MOUSE_MOVE) {
      me.state = this.view.getState(
        this.getCellAt(pt.x, pt.y, null, null, null, function (state) {
          return (
            state.shape == null ||
            state.shape.paintBackground != mxRectangleShape.prototype.paintBackground ||
            mxUtils.getValue(state.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1' ||
            (state.shape.fill != null && state.shape.fill != mxConstants.NONE)
          );
        })
      );
    }
  }

  return me;
};

/**
 * Function: getStateForEvent
 *
 * Returns the state for the given touch event.
 */
mxGraph.prototype.getStateForTouchEvent = function (evt) {
  var x = mxEvent.getClientX(evt);
  var y = mxEvent.getClientY(evt);

  // Dispatches the drop event to the graph which
  // consumes and executes the source function
  var pt = mxUtils.convertPoint(this.container, x, y);

  return this.view.getState(this.getCellAt(pt.x, pt.y));
};

/**
 * Function: isEventIgnored
 *
 * Returns true if the event should be ignored in <fireMouseEvent>.
 */
mxGraph.prototype.isEventIgnored = function (evtName, me, sender) {
  var mouseEvent = mxEvent.isMouseEvent(me.getEvent());
  var result = false;

  // Drops events that are fired more than once
  if (me.getEvent() == this.lastEvent) {
    result = true;
  } else {
    this.lastEvent = me.getEvent();
  }

  // Installs event listeners to capture the complete gesture from the event source
  // for non-MS touch events as a workaround for all events for the same geture being
  // fired from the event source even if that was removed from the DOM.
  if (this.eventSource != null && evtName != mxEvent.MOUSE_MOVE) {
    mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect);
    this.mouseMoveRedirect = null;
    this.mouseUpRedirect = null;
    this.eventSource = null;
  } else if (!mxClient.IS_GC && this.eventSource != null && me.getSource() != this.eventSource) {
    result = true;
  } else if (mxClient.IS_TOUCH && evtName == mxEvent.MOUSE_DOWN && !mouseEvent && !mxEvent.isPenEvent(me.getEvent())) {
    this.eventSource = me.getSource();

    this.mouseMoveRedirect = mxUtils.bind(this, function (evt) {
      this.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, this.getStateForTouchEvent(evt)));
    });
    this.mouseUpRedirect = mxUtils.bind(this, function (evt) {
      this.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, this.getStateForTouchEvent(evt)));
    });

    mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveRedirect, this.mouseUpRedirect);
  }

  // Factored out the workarounds for FF to make it easier to override/remove
  // Note this method has side-effects!
  if (this.isSyntheticEventIgnored(evtName, me, sender)) {
    result = true;
  }

  // Never fires mouseUp/-Down for double clicks
  if (!mxEvent.isPopupTrigger(this.lastEvent) && evtName != mxEvent.MOUSE_MOVE && this.lastEvent.detail == 2) {
    return true;
  }

  // Filters out of sequence events or mixed event types during a gesture
  if (evtName == mxEvent.MOUSE_UP && this.isMouseDown) {
    this.isMouseDown = false;
  } else if (evtName == mxEvent.MOUSE_DOWN && !this.isMouseDown) {
    this.isMouseDown = true;
    this.isMouseTrigger = mouseEvent;
  }
  // Drops mouse events that are fired during touch gestures as a workaround for Webkit
  // and mouse events that are not in sync with the current internal button state
  else if (
    !result &&
    (((!mxClient.IS_FF || evtName != mxEvent.MOUSE_MOVE) && this.isMouseDown && this.isMouseTrigger != mouseEvent) ||
      (evtName == mxEvent.MOUSE_DOWN && this.isMouseDown) ||
      (evtName == mxEvent.MOUSE_UP && !this.isMouseDown))
  ) {
    result = true;
  }

  if (!result && evtName == mxEvent.MOUSE_DOWN) {
    this.lastMouseX = me.getX();
    this.lastMouseY = me.getY();
  }

  return result;
};

/**
 * Function: isSyntheticEventIgnored
 *
 * Hook for ignoring synthetic mouse events after touchend in Firefox.
 */
mxGraph.prototype.isSyntheticEventIgnored = function (evtName, me, sender) {
  var result = false;
  var mouseEvent = mxEvent.isMouseEvent(me.getEvent());

  // LATER: This does not cover all possible cases that can go wrong in FF
  if (this.ignoreMouseEvents && mouseEvent && evtName != mxEvent.MOUSE_MOVE) {
    this.ignoreMouseEvents = evtName != mxEvent.MOUSE_UP;
    result = true;
  } else if (mxClient.IS_FF && !mouseEvent && evtName == mxEvent.MOUSE_UP) {
    this.ignoreMouseEvents = true;
  }

  return result;
};

/**
 * Function: isEventSourceIgnored
 *
 * Returns true if the event should be ignored in <fireMouseEvent>. This
 * implementation returns true for select, option and input (if not of type
 * checkbox, radio, button, submit or file) event sources if the event is not
 * a mouse event or a left mouse button press event.
 *
 * Parameters:
 *
 * evtName - The name of the event.
 * me - <mxMouseEvent> that should be ignored.
 */
mxGraph.prototype.isEventSourceIgnored = function (evtName, me) {
  var source = me.getSource();
  var name = source.nodeName != null ? source.nodeName.toLowerCase() : '';
  var candidate = !mxEvent.isMouseEvent(me.getEvent()) || mxEvent.isLeftMouseButton(me.getEvent());

  return (
    evtName == mxEvent.MOUSE_DOWN &&
    candidate &&
    (name == 'select' ||
      name == 'option' ||
      (name == 'input' &&
        source.type != 'checkbox' &&
        source.type != 'radio' &&
        source.type != 'button' &&
        source.type != 'submit' &&
        source.type != 'file'))
  );
};

/**
 * Function: getEventState
 *
 * Returns the <mxCellState> to be used when firing the mouse event for the
 * given state. This implementation returns the given state.
 *
 * Parameters:
 *
 * <mxCellState> - State whose event source should be returned.
 */
mxGraph.prototype.getEventState = function (state) {
  return state;
};

/**
 * Function: fireMouseEvent
 *
 * Dispatches the given event in the graph event dispatch loop. Possible
 * event names are <mxEvent.MOUSE_DOWN>, <mxEvent.MOUSE_MOVE> and
 * <mxEvent.MOUSE_UP>. All listeners are invoked for all events regardless
 * of the consumed state of the event.
 *
 * Parameters:
 *
 * evtName - String that specifies the type of event to be dispatched.
 * me - <mxMouseEvent> to be fired.
 * sender - Optional sender argument. Default is this.
 */
mxGraph.prototype.fireMouseEvent = function (evtName, me, sender) {
  if (this.isEventSourceIgnored(evtName, me)) {
    if (this.tooltipHandler != null) {
      this.tooltipHandler.hide();
    }

    return;
  }

  if (sender == null) {
    sender = this;
  }

  // Updates the graph coordinates in the event
  me = this.updateMouseEvent(me, evtName);

  // Detects and processes double taps for touch-based devices which do not have native double click events
  // or where detection of double click is not always possible (quirks, IE10+). Note that this can only handle
  // double clicks on cells because the sequence of events in IE prevents detection on the background, it fires
  // two mouse ups, one of which without a cell but no mousedown for the second click which means we cannot
  // detect which mouseup(s) are part of the first click, ie we do not know when the first click ends.
  if (
    (!this.nativeDblClickEnabled && !mxEvent.isPopupTrigger(me.getEvent())) ||
    (this.doubleTapEnabled &&
      mxClient.IS_TOUCH &&
      (mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())))
  ) {
    var currentTime = new Date().getTime();

    // NOTE: Second mouseDown for double click missing in quirks mode
    if (
      (!mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_DOWN) ||
      (mxClient.IS_QUIRKS && evtName == mxEvent.MOUSE_UP && !this.fireDoubleClick)
    ) {
      if (
        this.lastTouchEvent != null &&
        this.lastTouchEvent != me.getEvent() &&
        currentTime - this.lastTouchTime < this.doubleTapTimeout &&
        Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
        Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance &&
        this.doubleClickCounter < 2
      ) {
        this.doubleClickCounter++;
        var doubleClickFired = false;

        if (evtName == mxEvent.MOUSE_UP) {
          if (me.getCell() == this.lastTouchCell && this.lastTouchCell != null) {
            this.lastTouchTime = 0;
            var cell = this.lastTouchCell;
            this.lastTouchCell = null;

            // Fires native dblclick event via event source
            // NOTE: This fires two double click events on edges in quirks mode. While
            // trying to fix this, we realized that nativeDoubleClick can be disabled for
            // quirks and IE10+ (or we didn't find the case mentioned above where it
            // would not work), ie. all double clicks seem to be working without this.
            if (mxClient.IS_QUIRKS) {
              me.getSource().fireEvent('ondblclick');
            }

            this.dblClick(me.getEvent(), cell);
            doubleClickFired = true;
          }
        } else {
          this.fireDoubleClick = true;
          this.lastTouchTime = 0;
        }

        // Do not ignore mouse up in quirks in this case
        if (!mxClient.IS_QUIRKS || doubleClickFired) {
          mxEvent.consume(me.getEvent());
          return;
        }
      } else if (this.lastTouchEvent == null || this.lastTouchEvent != me.getEvent()) {
        this.lastTouchCell = me.getCell();
        this.lastTouchX = me.getX();
        this.lastTouchY = me.getY();
        this.lastTouchTime = currentTime;
        this.lastTouchEvent = me.getEvent();
        this.doubleClickCounter = 0;
      }
    } else if ((this.isMouseDown || evtName == mxEvent.MOUSE_UP) && this.fireDoubleClick) {
      this.fireDoubleClick = false;
      var cell = this.lastTouchCell;
      this.lastTouchCell = null;
      this.isMouseDown = false;

      // Workaround for Chrome/Safari not firing native double click events for double touch on background
      var valid =
        cell != null ||
        ((mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())) &&
          (mxClient.IS_GC || mxClient.IS_SF));

      if (
        valid &&
        Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
        Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance
      ) {
        this.dblClick(me.getEvent(), cell);
      } else {
        mxEvent.consume(me.getEvent());
      }

      return;
    }
  }

  if (!this.isEventIgnored(evtName, me, sender)) {
    // Updates the event state via getEventState
    me.state = this.getEventState(me.getState());
    this.fireEvent(new mxEventObject(mxEvent.FIRE_MOUSE_EVENT, 'eventName', evtName, 'event', me));

    if (
      mxClient.IS_OP ||
      mxClient.IS_SF ||
      mxClient.IS_GC ||
      mxClient.IS_IE11 ||
      (mxClient.IS_IE && mxClient.IS_SVG) ||
      me.getEvent().target != this.container
    ) {
      if (
        evtName == mxEvent.MOUSE_MOVE &&
        this.isMouseDown &&
        this.autoScroll &&
        !mxEvent.isMultiTouchEvent(me.getEvent)
      ) {
        this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend);
      } else if (
        evtName == mxEvent.MOUSE_UP &&
        this.ignoreScrollbars &&
        this.translateToScrollPosition &&
        (this.container.scrollLeft != 0 || this.container.scrollTop != 0)
      ) {
        var s = this.view.scale;
        var tr = this.view.translate;
        this.view.setTranslate(tr.x - this.container.scrollLeft / s, tr.y - this.container.scrollTop / s);
        this.container.scrollLeft = 0;
        this.container.scrollTop = 0;
      }

      if (this.mouseListeners != null) {
        var args = [sender, me];

        // Does not change returnValue in Opera
        if (!me.getEvent().preventDefault) {
          me.getEvent().returnValue = true;
        }

        for (var i = 0; i < this.mouseListeners.length; i++) {
          var l = this.mouseListeners[i];

          if (evtName == mxEvent.MOUSE_DOWN) {
            l.mouseDown.apply(l, args);
          } else if (evtName == mxEvent.MOUSE_MOVE) {
            l.mouseMove.apply(l, args);
          } else if (evtName == mxEvent.MOUSE_UP) {
            l.mouseUp.apply(l, args);
          }
        }
      }

      // Invokes the click handler
      if (evtName == mxEvent.MOUSE_UP) {
        this.click(me);
      }
    }

    // Detects tapAndHold events using a timer
    if (
      (mxEvent.isTouchEvent(me.getEvent()) || mxEvent.isPenEvent(me.getEvent())) &&
      evtName == mxEvent.MOUSE_DOWN &&
      this.tapAndHoldEnabled &&
      !this.tapAndHoldInProgress
    ) {
      this.tapAndHoldInProgress = true;
      this.initialTouchX = me.getGraphX();
      this.initialTouchY = me.getGraphY();

      var handler = function () {
        if (this.tapAndHoldValid) {
          this.tapAndHold(me);
        }

        this.tapAndHoldInProgress = false;
        this.tapAndHoldValid = false;
      };

      if (this.tapAndHoldThread) {
        window.clearTimeout(this.tapAndHoldThread);
      }

      this.tapAndHoldThread = window.setTimeout(mxUtils.bind(this, handler), this.tapAndHoldDelay);
      this.tapAndHoldValid = true;
    } else if (evtName == mxEvent.MOUSE_UP) {
      this.tapAndHoldInProgress = false;
      this.tapAndHoldValid = false;
    } else if (this.tapAndHoldValid) {
      this.tapAndHoldValid =
        Math.abs(this.initialTouchX - me.getGraphX()) < this.tolerance &&
        Math.abs(this.initialTouchY - me.getGraphY()) < this.tolerance;
    }

    // Stops editing for all events other than from cellEditor
    if (evtName == mxEvent.MOUSE_DOWN && this.isEditing() && !this.cellEditor.isEventSource(me.getEvent())) {
      this.stopEditing(!this.isInvokesStopCellEditing());
    }

    this.consumeMouseEvent(evtName, me, sender);
  }
};

/**
 * Function: consumeMouseEvent
 *
 * Consumes the given <mxMouseEvent> if it's a touchStart event.
 */
mxGraph.prototype.consumeMouseEvent = function (evtName, me, sender) {
  // Workaround for duplicate click in Windows 8 with Chrome/FF/Opera with touch
  if (evtName == mxEvent.MOUSE_DOWN && mxEvent.isTouchEvent(me.getEvent())) {
    me.consume(false);
  }
};

/**
 * Function: fireGestureEvent
 *
 * Dispatches a <mxEvent.GESTURE> event. The following example will resize the
 * cell under the mouse based on the scale property of the native touch event.
 *
 * (code)
 * graph.addListener(mxEvent.GESTURE, function(sender, eo)
 * {
 *   var evt = eo.getProperty('event');
 *   var state = graph.view.getState(eo.getProperty('cell'));
 *
 *   if (graph.isEnabled() && graph.isCellResizable(state.cell) && Math.abs(1 - evt.scale) > 0.2)
 *   {
 *     var scale = graph.view.scale;
 *     var tr = graph.view.translate;
 *
 *     var w = state.width * evt.scale;
 *     var h = state.height * evt.scale;
 *     var x = state.x - (w - state.width) / 2;
 *     var y = state.y - (h - state.height) / 2;
 *
 *     var bounds = new mxRectangle(graph.snap(x / scale) - tr.x,
 *     		graph.snap(y / scale) - tr.y, graph.snap(w / scale), graph.snap(h / scale));
 *     graph.resizeCell(state.cell, bounds);
 *     eo.consume();
 *   }
 * });
 * (end)
 *
 * Parameters:
 *
 * evt - Gestureend event that represents the gesture.
 * cell - Optional <mxCell> associated with the gesture.
 */
mxGraph.prototype.fireGestureEvent = function (evt, cell) {
  // Resets double tap event handling when gestures take place
  this.lastTouchTime = 0;
  this.fireEvent(new mxEventObject(mxEvent.GESTURE, 'event', evt, 'cell', cell));
};

/**
 * Function: destroy
 *
 * Destroys the graph and all its resources.
 */
mxGraph.prototype.destroy = function () {
  if (!this.destroyed) {
    this.destroyed = true;

    if (this.tooltipHandler != null) {
      this.tooltipHandler.destroy();
    }

    if (this.selectionCellsHandler != null) {
      this.selectionCellsHandler.destroy();
    }

    if (this.panningHandler != null) {
      this.panningHandler.destroy();
    }

    if (this.popupMenuHandler != null) {
      this.popupMenuHandler.destroy();
    }

    if (this.connectionHandler != null) {
      this.connectionHandler.destroy();
    }

    if (this.graphHandler != null) {
      this.graphHandler.destroy();
    }

    if (this.cellEditor != null) {
      this.cellEditor.destroy();
    }

    if (this.view != null) {
      this.view.destroy();
    }

    if (this.model != null && this.graphModelChangeListener != null) {
      this.model.removeListener(this.graphModelChangeListener);
      this.graphModelChangeListener = null;
    }

    this.container = null;
  }
};

/**
 * view/mxCellOverlay.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCellOverlay
 *
 * Extends <mxEventSource> to implement a graph overlay, represented by an icon
 * and a tooltip. Overlays can handle and fire <click> events and are added to
 * the graph using <mxGraph.addCellOverlay>, and removed using
 * <mxGraph.removeCellOverlay>, or <mxGraph.removeCellOverlays> to remove all overlays.
 * The <mxGraph.getCellOverlays> function returns the array of overlays for a given
 * cell in a graph. If multiple overlays exist for the same cell, then
 * <getBounds> should be overridden in at least one of the overlays.
 *
 * Overlays appear on top of all cells in a special layer. If this is not
 * desirable, then the image must be rendered as part of the shape or label of
 * the cell instead.
 *
 * Example:
 *
 * The following adds a new overlays for a given vertex and selects the cell
 * if the overlay is clicked.
 *
 * (code)
 * var overlay = new mxCellOverlay(img, html);
 * graph.addCellOverlay(vertex, overlay);
 * overlay.addListener(mxEvent.CLICK, function(sender, evt)
 * {
 *   var cell = evt.getProperty('cell');
 *   graph.setSelectionCell(cell);
 * });
 * (end)
 *
 * For cell overlays to be printed use <mxPrintPreview.printOverlays>.
 *
 * Event: mxEvent.CLICK
 *
 * Fires when the user clicks on the overlay. The <code>event</code> property
 * contains the corresponding mouse event and the <code>cell</code> property
 * contains the cell. For touch devices this is fired if the element receives
 * a touchend event.
 *
 * Constructor: mxCellOverlay
 *
 * Constructs a new overlay using the given image and tooltip.
 *
 * Parameters:
 *
 * image - <mxImage> that represents the icon to be displayed.
 * tooltip - Optional string that specifies the tooltip.
 * align - Optional horizontal alignment for the overlay. Possible
 * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>
 * (default).
 * verticalAlign - Vertical alignment for the overlay. Possible
 * values are <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>
 * (default).
 */
function mxCellOverlay(image, tooltip, align, verticalAlign, offset, cursor) {
  this.image = image;
  this.tooltip = tooltip;
  this.align = align != null ? align : this.align;
  this.verticalAlign = verticalAlign != null ? verticalAlign : this.verticalAlign;
  this.offset = offset != null ? offset : new mxPoint();
  this.cursor = cursor != null ? cursor : 'help';
}

/**
 * Extends mxEventSource.
 */
mxCellOverlay.prototype = new mxEventSource();
mxCellOverlay.prototype.constructor = mxCellOverlay;

/**
 * Variable: image
 *
 * Holds the <mxImage> to be used as the icon.
 */
mxCellOverlay.prototype.image = null;

/**
 * Variable: tooltip
 *
 * Holds the optional string to be used as the tooltip.
 */
mxCellOverlay.prototype.tooltip = null;

/**
 * Variable: align
 *
 * Holds the horizontal alignment for the overlay. Default is
 * <mxConstants.ALIGN_RIGHT>. For edges, the overlay always appears in the
 * center of the edge.
 */
mxCellOverlay.prototype.align = mxConstants.ALIGN_RIGHT;

/**
 * Variable: verticalAlign
 *
 * Holds the vertical alignment for the overlay. Default is
 * <mxConstants.ALIGN_BOTTOM>. For edges, the overlay always appears in the
 * center of the edge.
 */
mxCellOverlay.prototype.verticalAlign = mxConstants.ALIGN_BOTTOM;

/**
 * Variable: offset
 *
 * Holds the offset as an <mxPoint>. The offset will be scaled according to the
 * current scale.
 */
mxCellOverlay.prototype.offset = null;

/**
 * Variable: cursor
 *
 * Holds the cursor for the overlay. Default is 'help'.
 */
mxCellOverlay.prototype.cursor = null;

/**
 * Variable: defaultOverlap
 *
 * Defines the overlapping for the overlay, that is, the proportional distance
 * from the origin to the point defined by the alignment. Default is 0.5.
 */
mxCellOverlay.prototype.defaultOverlap = 0.5;

/**
 * Function: getBounds
 *
 * Returns the bounds of the overlay for the given <mxCellState> as an
 * <mxRectangle>. This should be overridden when using multiple overlays
 * per cell so that the overlays do not overlap.
 *
 * The following example will place the overlay along an edge (where
 * x=[-1..1] from the start to the end of the edge and y is the
 * orthogonal offset in px).
 *
 * (code)
 * overlay.getBounds = function(state)
 * {
 *   var bounds = mxCellOverlay.prototype.getBounds.apply(this, arguments);
 *
 *   if (state.view.graph.getModel().isEdge(state.cell))
 *   {
 *     var pt = state.view.getPoint(state, {x: 0, y: 0, relative: true});
 *
 *     bounds.x = pt.x - bounds.width / 2;
 *     bounds.y = pt.y - bounds.height / 2;
 *   }
 *
 *   return bounds;
 * };
 * (end)
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the current state of the
 * associated cell.
 */
mxCellOverlay.prototype.getBounds = function (state) {
  var isEdge = state.view.graph.getModel().isEdge(state.cell);
  var s = state.view.scale;
  var pt = null;

  var w = this.image.width;
  var h = this.image.height;

  if (isEdge) {
    var pts = state.absolutePoints;

    if (pts.length % 2 == 1) {
      pt = pts[Math.floor(pts.length / 2)];
    } else {
      var idx = pts.length / 2;
      var p0 = pts[idx - 1];
      var p1 = pts[idx];
      pt = new mxPoint(p0.x + (p1.x - p0.x) / 2, p0.y + (p1.y - p0.y) / 2);
    }
  } else {
    pt = new mxPoint();

    if (this.align == mxConstants.ALIGN_LEFT) {
      pt.x = state.x;
    } else if (this.align == mxConstants.ALIGN_CENTER) {
      pt.x = state.x + state.width / 2;
    } else {
      pt.x = state.x + state.width;
    }

    if (this.verticalAlign == mxConstants.ALIGN_TOP) {
      pt.y = state.y;
    } else if (this.verticalAlign == mxConstants.ALIGN_MIDDLE) {
      pt.y = state.y + state.height / 2;
    } else {
      pt.y = state.y + state.height;
    }
  }

  return new mxRectangle(
    Math.round(pt.x - (w * this.defaultOverlap - this.offset.x) * s),
    Math.round(pt.y - (h * this.defaultOverlap - this.offset.y) * s),
    w * s,
    h * s
  );
};

/**
 * Function: toString
 *
 * Returns the textual representation of the overlay to be used as the
 * tooltip. This implementation returns <tooltip>.
 */
mxCellOverlay.prototype.toString = function () {
  return this.tooltip;
};

/**
 * view/mxOutline.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxOutline
 *
 * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
 * to enable updates while the source graph is panning.
 *
 * Example:
 *
 * (code)
 * var outline = new mxOutline(graph, div);
 * (end)
 *
 * If an outline is used in an <mxWindow> in IE8 standards mode, the following
 * code makes sure that the shadow filter is not inherited and that any
 * transparent elements in the graph do not show the page background, but the
 * background of the graph container.
 *
 * (code)
 * if (document.documentMode == 8)
 * {
 *   container.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
 * }
 * (end)
 *
 * To move the graph to the top, left corner the following code can be used.
 *
 * (code)
 * var scale = graph.view.scale;
 * var bounds = graph.getGraphBounds();
 * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
 * (end)
 *
 * To toggle the suspended mode, the following can be used.
 *
 * (code)
 * outline.suspended = !outln.suspended;
 * if (!outline.suspended)
 * {
 *   outline.update(true);
 * }
 * (end)
 *
 * Constructor: mxOutline
 *
 * Constructs a new outline for the specified graph inside the given
 * container.
 *
 * Parameters:
 *
 * source - <mxGraph> to create the outline for.
 * container - DOM node that will contain the outline.
 */
function mxOutline(source, container) {
  this.source = source;

  if (container != null) {
    this.init(container);
  }
}

/**
 * Function: source
 *
 * Reference to the source <mxGraph>.
 */
mxOutline.prototype.source = null;

/**
 * Function: outline
 *
 * Reference to the <mxGraph> that renders the outline.
 */
mxOutline.prototype.outline = null;

/**
 * Function: graphRenderHint
 *
 * Renderhint to be used for the outline graph. Default is faster.
 */
mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxOutline.prototype.enabled = true;

/**
 * Variable: showViewport
 *
 * Specifies a viewport rectangle should be shown. Default is true.
 */
mxOutline.prototype.showViewport = true;

/**
 * Variable: border
 *
 * Border to be added at the bottom and right. Default is 10.
 */
mxOutline.prototype.border = 10;

/**
 * Variable: enabled
 *
 * Specifies the size of the sizer handler. Default is 8.
 */
mxOutline.prototype.sizerSize = 8;

/**
 * Variable: labelsVisible
 *
 * Specifies if labels should be visible in the outline. Default is false.
 */
mxOutline.prototype.labelsVisible = false;

/**
 * Variable: updateOnPan
 *
 * Specifies if <update> should be called for <mxEvent.PAN> in the source
 * graph. Default is false.
 */
mxOutline.prototype.updateOnPan = false;

/**
 * Variable: sizerImage
 *
 * Optional <mxImage> to be used for the sizer. Default is null.
 */
mxOutline.prototype.sizerImage = null;

/**
 * Variable: minScale
 *
 * Minimum scale to be used. Default is 0.0001.
 */
mxOutline.prototype.minScale = 0.0001;

/**
 * Variable: suspended
 *
 * Optional boolean flag to suspend updates. Default is false. This flag will
 * also suspend repaints of the outline. To toggle this switch, use the
 * following code.
 *
 * (code)
 * nav.suspended = !nav.suspended;
 *
 * if (!nav.suspended)
 * {
 *   nav.update(true);
 * }
 * (end)
 */
mxOutline.prototype.suspended = false;

/**
 * Variable: forceVmlHandles
 *
 * Specifies if VML should be used to render the handles in this control. This
 * is true for IE8 standards mode and false for all other browsers and modes.
 * This is a workaround for rendering issues of HTML elements over elements
 * with filters in IE 8 standards mode.
 */
mxOutline.prototype.forceVmlHandles = document.documentMode == 8;

/**
 * Function: createGraph
 *
 * Creates the <mxGraph> used in the outline.
 */
mxOutline.prototype.createGraph = function (container) {
  var graph = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());
  graph.foldingEnabled = false;
  graph.autoScroll = false;

  return graph;
};

/**
 * Function: init
 *
 * Initializes the outline inside the given container.
 */
mxOutline.prototype.init = function (container) {
  this.outline = this.createGraph(container);

  // Do not repaint when suspended
  var outlineGraphModelChanged = this.outline.graphModelChanged;
  this.outline.graphModelChanged = mxUtils.bind(this, function (changes) {
    if (!this.suspended && this.outline != null) {
      outlineGraphModelChanged.apply(this.outline, arguments);
    }
  });

  // Enables faster painting in SVG
  if (mxClient.IS_SVG) {
    var node = this.outline.getView().getCanvas().parentNode;
    node.setAttribute('shape-rendering', 'optimizeSpeed');
    node.setAttribute('image-rendering', 'optimizeSpeed');
  }

  // Hides cursors and labels
  this.outline.labelsVisible = this.labelsVisible;
  this.outline.setEnabled(false);

  this.updateHandler = mxUtils.bind(this, function (sender, evt) {
    if (!this.suspended && !this.active) {
      this.update();
    }
  });

  // Updates the scale of the outline after a change of the main graph
  this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
  this.outline.addMouseListener(this);

  // Adds listeners to keep the outline in sync with the source graph
  var view = this.source.getView();
  view.addListener(mxEvent.SCALE, this.updateHandler);
  view.addListener(mxEvent.TRANSLATE, this.updateHandler);
  view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
  view.addListener(mxEvent.DOWN, this.updateHandler);
  view.addListener(mxEvent.UP, this.updateHandler);

  // Updates blue rectangle on scroll
  mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);

  this.panHandler = mxUtils.bind(this, function (sender) {
    if (this.updateOnPan) {
      this.updateHandler.apply(this, arguments);
    }
  });
  this.source.addListener(mxEvent.PAN, this.panHandler);

  // Refreshes the graph in the outline after a refresh of the main graph
  this.refreshHandler = mxUtils.bind(this, function (sender) {
    this.outline.setStylesheet(this.source.getStylesheet());
    this.outline.refresh();
  });
  this.source.addListener(mxEvent.REFRESH, this.refreshHandler);

  // Creates the blue rectangle for the viewport
  this.bounds = new mxRectangle(0, 0, 0, 0);
  this.selectionBorder = new mxRectangleShape(
    this.bounds,
    null,
    mxConstants.OUTLINE_COLOR,
    mxConstants.OUTLINE_STROKEWIDTH
  );
  this.selectionBorder.dialect = this.outline.dialect;

  if (this.forceVmlHandles) {
    this.selectionBorder.isHtmlAllowed = function () {
      return false;
    };
  }

  this.selectionBorder.init(this.outline.getView().getOverlayPane());

  // Handles event by catching the initial pointer start and then listening to the
  // complete gesture on the event target. This is needed because all the events
  // are routed via the initial element even if that element is removed from the
  // DOM, which happens when we repaint the selection border and zoom handles.
  var handler = mxUtils.bind(this, function (evt) {
    var t = mxEvent.getSource(evt);

    var redirect = mxUtils.bind(this, function (evt) {
      this.outline.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
    });

    var redirect2 = mxUtils.bind(this, function (evt) {
      mxEvent.removeGestureListeners(t, null, redirect, redirect2);
      this.outline.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
    });

    mxEvent.addGestureListeners(t, null, redirect, redirect2);
    this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
  });

  mxEvent.addGestureListeners(this.selectionBorder.node, handler);

  // Creates a small blue rectangle for sizing (sizer handle)
  this.sizer = this.createSizer();

  if (this.forceVmlHandles) {
    this.sizer.isHtmlAllowed = function () {
      return false;
    };
  }

  this.sizer.init(this.outline.getView().getOverlayPane());

  if (this.enabled) {
    this.sizer.node.style.cursor = 'nwse-resize';
  }

  mxEvent.addGestureListeners(this.sizer.node, handler);

  this.selectionBorder.node.style.display = this.showViewport ? '' : 'none';
  this.sizer.node.style.display = this.selectionBorder.node.style.display;
  this.selectionBorder.node.style.cursor = 'move';

  this.update(false);
};

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxOutline.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 *
 * Parameters:
 *
 * value - Boolean that specifies the new enabled state.
 */
mxOutline.prototype.setEnabled = function (value) {
  this.enabled = value;
};

/**
 * Function: setZoomEnabled
 *
 * Enables or disables the zoom handling by showing or hiding the respective
 * handle.
 *
 * Parameters:
 *
 * value - Boolean that specifies the new enabled state.
 */
mxOutline.prototype.setZoomEnabled = function (value) {
  this.sizer.node.style.visibility = value ? 'visible' : 'hidden';
};

/**
 * Function: refresh
 *
 * Invokes <update> and revalidate the outline. This method is deprecated.
 */
mxOutline.prototype.refresh = function () {
  this.update(true);
};

/**
 * Function: createSizer
 *
 * Creates the shape used as the sizer.
 */
mxOutline.prototype.createSizer = function () {
  if (this.sizerImage != null) {
    var sizer = new mxImageShape(
      new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height),
      this.sizerImage.src
    );
    sizer.dialect = this.outline.dialect;

    return sizer;
  } else {
    var sizer = new mxRectangleShape(
      new mxRectangle(0, 0, this.sizerSize, this.sizerSize),
      mxConstants.OUTLINE_HANDLE_FILLCOLOR,
      mxConstants.OUTLINE_HANDLE_STROKECOLOR
    );
    sizer.dialect = this.outline.dialect;

    return sizer;
  }
};

/**
 * Function: getSourceContainerSize
 *
 * Returns the size of the source container.
 */
mxOutline.prototype.getSourceContainerSize = function () {
  return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight);
};

/**
 * Function: getOutlineOffset
 *
 * Returns the offset for drawing the outline graph.
 */
mxOutline.prototype.getOutlineOffset = function (scale) {
  return null;
};

/**
 * Function: getSourceGraphBounds
 *
 * Returns the graph bound boxing of the source.
 */
mxOutline.prototype.getSourceGraphBounds = function () {
  return this.source.getGraphBounds();
};

/**
 * Function: update
 *
 * Updates the outline.
 */
mxOutline.prototype.update = function (revalidate) {
  if (this.source != null && this.source.container != null && this.outline != null && this.outline.container != null) {
    var sourceScale = this.source.view.scale;
    var scaledGraphBounds = this.getSourceGraphBounds();
    var unscaledGraphBounds = new mxRectangle(
      scaledGraphBounds.x / sourceScale + this.source.panDx,
      scaledGraphBounds.y / sourceScale + this.source.panDy,
      scaledGraphBounds.width / sourceScale,
      scaledGraphBounds.height / sourceScale
    );

    var unscaledFinderBounds = new mxRectangle(
      0,
      0,
      this.source.container.clientWidth / sourceScale,
      this.source.container.clientHeight / sourceScale
    );

    var union = unscaledGraphBounds.clone();
    union.add(unscaledFinderBounds);

    // Zooms to the scrollable area if that is bigger than the graph
    var size = this.getSourceContainerSize();
    var completeWidth = Math.max(size.width / sourceScale, union.width);
    var completeHeight = Math.max(size.height / sourceScale, union.height);

    var availableWidth = Math.max(0, this.outline.container.clientWidth - this.border);
    var availableHeight = Math.max(0, this.outline.container.clientHeight - this.border);

    var outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight);
    var scale = isNaN(outlineScale) ? this.minScale : Math.max(this.minScale, outlineScale);

    if (scale > 0) {
      if (this.outline.getView().scale != scale) {
        this.outline.getView().scale = scale;
        revalidate = true;
      }

      var navView = this.outline.getView();

      if (navView.currentRoot != this.source.getView().currentRoot) {
        navView.setCurrentRoot(this.source.getView().currentRoot);
      }

      var t = this.source.view.translate;
      var tx = t.x + this.source.panDx;
      var ty = t.y + this.source.panDy;

      var off = this.getOutlineOffset(scale);

      if (off != null) {
        tx += off.x;
        ty += off.y;
      }

      if (unscaledGraphBounds.x < 0) {
        tx = tx - unscaledGraphBounds.x;
      }
      if (unscaledGraphBounds.y < 0) {
        ty = ty - unscaledGraphBounds.y;
      }

      if (navView.translate.x != tx || navView.translate.y != ty) {
        navView.translate.x = tx;
        navView.translate.y = ty;
        revalidate = true;
      }

      // Prepares local variables for computations
      var t2 = navView.translate;
      scale = this.source.getView().scale;
      var scale2 = scale / navView.scale;
      var scale3 = 1.0 / navView.scale;
      var container = this.source.container;

      // Updates the bounds of the viewrect in the navigation
      this.bounds = new mxRectangle(
        (t2.x - t.x - this.source.panDx) / scale3,
        (t2.y - t.y - this.source.panDy) / scale3,
        container.clientWidth / scale2,
        container.clientHeight / scale2
      );

      // Adds the scrollbar offset to the finder
      this.bounds.x += (this.source.container.scrollLeft * navView.scale) / scale;
      this.bounds.y += (this.source.container.scrollTop * navView.scale) / scale;

      var b = this.selectionBorder.bounds;

      if (
        b.x != this.bounds.x ||
        b.y != this.bounds.y ||
        b.width != this.bounds.width ||
        b.height != this.bounds.height
      ) {
        this.selectionBorder.bounds = this.bounds;
        this.selectionBorder.redraw();
      }

      // Updates the bounds of the zoom handle at the bottom right
      var b = this.sizer.bounds;
      var b2 = new mxRectangle(
        this.bounds.x + this.bounds.width - b.width / 2,
        this.bounds.y + this.bounds.height - b.height / 2,
        b.width,
        b.height
      );

      if (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height) {
        this.sizer.bounds = b2;

        // Avoids update of visibility in redraw for VML
        if (this.sizer.node.style.visibility != 'hidden') {
          this.sizer.redraw();
        }
      }

      if (revalidate) {
        this.outline.view.revalidate();
      }
    }
  }
};

/**
 * Function: mouseDown
 *
 * Handles the event by starting a translation or zoom.
 */
mxOutline.prototype.mouseDown = function (sender, me) {
  if (this.enabled && this.showViewport) {
    var tol = !mxEvent.isMouseEvent(me.getEvent()) ? this.source.tolerance : 0;
    var hit =
      this.source.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)
        ? new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol)
        : null;
    this.zoom = me.isSource(this.sizer) || (hit != null && mxUtils.intersects(shape.bounds, hit));
    this.startX = me.getX();
    this.startY = me.getY();
    this.active = true;

    if (this.source.useScrollbarsForPanning && mxUtils.hasScrollbars(this.source.container)) {
      this.dx0 = this.source.container.scrollLeft;
      this.dy0 = this.source.container.scrollTop;
    } else {
      this.dx0 = 0;
      this.dy0 = 0;
    }
  }

  me.consume();
};

/**
 * Function: mouseMove
 *
 * Handles the event by previewing the viewrect in <graph> and updating the
 * rectangle that represents the viewrect in the outline.
 */
mxOutline.prototype.mouseMove = function (sender, me) {
  if (this.active) {
    this.selectionBorder.node.style.display = this.showViewport ? '' : 'none';
    this.sizer.node.style.display = this.selectionBorder.node.style.display;

    var delta = this.getTranslateForEvent(me);
    var dx = delta.x;
    var dy = delta.y;
    var bounds = null;

    if (!this.zoom) {
      // Previews the panning on the source graph
      var scale = this.outline.getView().scale;
      bounds = new mxRectangle(this.bounds.x + dx, this.bounds.y + dy, this.bounds.width, this.bounds.height);
      this.selectionBorder.bounds = bounds;
      this.selectionBorder.redraw();
      dx /= scale;
      dx *= this.source.getView().scale;
      dy /= scale;
      dy *= this.source.getView().scale;
      this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
    } else {
      // Does *not* preview zooming on the source graph
      var container = this.source.container;
      var viewRatio = container.clientWidth / container.clientHeight;
      dy = dx / viewRatio;
      bounds = new mxRectangle(
        this.bounds.x,
        this.bounds.y,
        Math.max(1, this.bounds.width + dx),
        Math.max(1, this.bounds.height + dy)
      );
      this.selectionBorder.bounds = bounds;
      this.selectionBorder.redraw();
    }

    // Updates the zoom handle
    var b = this.sizer.bounds;
    this.sizer.bounds = new mxRectangle(
      bounds.x + bounds.width - b.width / 2,
      bounds.y + bounds.height - b.height / 2,
      b.width,
      b.height
    );

    // Avoids update of visibility in redraw for VML
    if (this.sizer.node.style.visibility != 'hidden') {
      this.sizer.redraw();
    }

    me.consume();
  }
};

/**
 * Function: getTranslateForEvent
 *
 * Gets the translate for the given mouse event. Here is an example to limit
 * the outline to stay within positive coordinates:
 *
 * (code)
 * outline.getTranslateForEvent = function(me)
 * {
 *   var pt = new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
 *
 *   if (!this.zoom)
 *   {
 *     var tr = this.source.view.translate;
 *     pt.x = Math.max(tr.x * this.outline.view.scale, pt.x);
 *     pt.y = Math.max(tr.y * this.outline.view.scale, pt.y);
 *   }
 *
 *   return pt;
 * };
 * (end)
 */
mxOutline.prototype.getTranslateForEvent = function (me) {
  return new mxPoint(me.getX() - this.startX, me.getY() - this.startY);
};

/**
 * Function: mouseUp
 *
 * Handles the event by applying the translation or zoom to <graph>.
 */
mxOutline.prototype.mouseUp = function (sender, me) {
  if (this.active) {
    var delta = this.getTranslateForEvent(me);
    var dx = delta.x;
    var dy = delta.y;

    if (Math.abs(dx) > 0 || Math.abs(dy) > 0) {
      if (!this.zoom) {
        // Applies the new translation if the source
        // has no scrollbars
        if (!this.source.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.source.container)) {
          this.source.panGraph(0, 0);
          dx /= this.outline.getView().scale;
          dy /= this.outline.getView().scale;
          var t = this.source.getView().translate;
          this.source.getView().setTranslate(t.x - dx, t.y - dy);
        }
      } else {
        // Applies the new zoom
        var w = this.selectionBorder.bounds.width;
        var scale = this.source.getView().scale;
        this.source.zoomTo(Math.max(this.minScale, scale - (dx * scale) / w), false);
      }

      this.update();
      me.consume();
    }

    // Resets the state of the handler
    this.index = null;
    this.active = false;
  }
};

/**
 * Function: destroy
 *
 * Destroy this outline and removes all listeners from <source>.
 */
mxOutline.prototype.destroy = function () {
  if (this.source != null) {
    this.source.removeListener(this.panHandler);
    this.source.removeListener(this.refreshHandler);
    this.source.getModel().removeListener(this.updateHandler);
    this.source.getView().removeListener(this.updateHandler);
    mxEvent.removeListener(this.source.container, 'scroll', this.updateHandler);
    this.source = null;
  }

  if (this.outline != null) {
    this.outline.removeMouseListener(this);
    this.outline.destroy();
    this.outline = null;
  }

  if (this.selectionBorder != null) {
    this.selectionBorder.destroy();
    this.selectionBorder = null;
  }

  if (this.sizer != null) {
    this.sizer.destroy();
    this.sizer = null;
  }
};

/**
 * view/mxMultiplicity.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxMultiplicity
 *
 * Defines invalid connections along with the error messages that they produce.
 * To add or remove rules on a graph, you must add/remove instances of this
 * class to <mxGraph.multiplicities>.
 *
 * Example:
 *
 * (code)
 * graph.multiplicities.push(new mxMultiplicity(
 *   true, 'rectangle', null, null, 0, 2, ['circle'],
 *   'Only 2 targets allowed',
 *   'Only circle targets allowed'));
 * (end)
 *
 * Defines a rule where each rectangle must be connected to no more than 2
 * circles and no other types of targets are allowed.
 *
 * Constructor: mxMultiplicity
 *
 * Instantiate class mxMultiplicity in order to describe allowed
 * connections in a graph. Not all constraints can be enforced while
 * editing, some must be checked at validation time. The <countError> and
 * <typeError> are treated as resource keys in <mxResources>.
 *
 * Parameters:
 *
 * source - Boolean indicating if this rule applies to the source or target
 * terminal.
 * type - Type of the source or target terminal that this rule applies to.
 * See <type> for more information.
 * attr - Optional attribute name to match the source or target terminal.
 * value - Optional attribute value to match the source or target terminal.
 * min - Minimum number of edges for this rule. Default is 1.
 * max - Maximum number of edges for this rule. n means infinite. Default
 * is n.
 * validNeighbors - Array of types of the opposite terminal for which this
 * rule applies.
 * countError - Error to be displayed for invalid number of edges.
 * typeError - Error to be displayed for invalid opposite terminals.
 * validNeighborsAllowed - Optional boolean indicating if the array of
 * opposite types should be valid or invalid.
 */
function mxMultiplicity(
  source,
  type,
  attr,
  value,
  min,
  max,
  validNeighbors,
  countError,
  typeError,
  validNeighborsAllowed
) {
  this.source = source;
  this.type = type;
  this.attr = attr;
  this.value = value;
  this.min = min != null ? min : 0;
  this.max = max != null ? max : 'n';
  this.validNeighbors = validNeighbors;
  this.countError = mxResources.get(countError) || countError;
  this.typeError = mxResources.get(typeError) || typeError;
  this.validNeighborsAllowed = validNeighborsAllowed != null ? validNeighborsAllowed : true;
}

/**
 * Variable: type
 *
 * Defines the type of the source or target terminal. The type is a string
 * passed to <mxUtils.isNode> together with the source or target vertex
 * value as the first argument.
 */
mxMultiplicity.prototype.type = null;

/**
 * Variable: attr
 *
 * Optional string that specifies the attributename to be passed to
 * <mxUtils.isNode> to check if the rule applies to a cell.
 */
mxMultiplicity.prototype.attr = null;

/**
 * Variable: value
 *
 * Optional string that specifies the value of the attribute to be passed
 * to <mxUtils.isNode> to check if the rule applies to a cell.
 */
mxMultiplicity.prototype.value = null;

/**
 * Variable: source
 *
 * Boolean that specifies if the rule is applied to the source or target
 * terminal of an edge.
 */
mxMultiplicity.prototype.source = null;

/**
 * Variable: min
 *
 * Defines the minimum number of connections for which this rule applies.
 * Default is 0.
 */
mxMultiplicity.prototype.min = null;

/**
 * Variable: max
 *
 * Defines the maximum number of connections for which this rule applies.
 * A value of 'n' means unlimited times. Default is 'n'.
 */
mxMultiplicity.prototype.max = null;

/**
 * Variable: validNeighbors
 *
 * Holds an array of strings that specify the type of neighbor for which
 * this rule applies. The strings are used in <mxCell.is> on the opposite
 * terminal to check if the rule applies to the connection.
 */
mxMultiplicity.prototype.validNeighbors = null;

/**
 * Variable: validNeighborsAllowed
 *
 * Boolean indicating if the list of validNeighbors are those that are allowed
 * for this rule or those that are not allowed for this rule.
 */
mxMultiplicity.prototype.validNeighborsAllowed = true;

/**
 * Variable: countError
 *
 * Holds the localized error message to be displayed if the number of
 * connections for which the rule applies is smaller than <min> or greater
 * than <max>.
 */
mxMultiplicity.prototype.countError = null;

/**
 * Variable: typeError
 *
 * Holds the localized error message to be displayed if the type of the
 * neighbor for a connection does not match the rule.
 */
mxMultiplicity.prototype.typeError = null;

/**
 * Function: check
 *
 * Checks the multiplicity for the given arguments and returns the error
 * for the given connection or null if the multiplicity does not apply.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph> instance.
 * edge - <mxCell> that represents the edge to validate.
 * source - <mxCell> that represents the source terminal.
 * target - <mxCell> that represents the target terminal.
 * sourceOut - Number of outgoing edges from the source terminal.
 * targetIn - Number of incoming edges for the target terminal.
 */
mxMultiplicity.prototype.check = function (graph, edge, source, target, sourceOut, targetIn) {
  var error = '';

  if (
    (this.source && this.checkTerminal(graph, source, edge)) ||
    (!this.source && this.checkTerminal(graph, target, edge))
  ) {
    if (
      this.countError != null &&
      ((this.source && (this.max == 0 || sourceOut >= this.max)) ||
        (!this.source && (this.max == 0 || targetIn >= this.max)))
    ) {
      error += this.countError + '\n';
    }

    if (this.validNeighbors != null && this.typeError != null && this.validNeighbors.length > 0) {
      var isValid = this.checkNeighbors(graph, edge, source, target);

      if (!isValid) {
        error += this.typeError + '\n';
      }
    }
  }

  return error.length > 0 ? error : null;
};

/**
 * Function: checkNeighbors
 *
 * Checks if there are any valid neighbours in <validNeighbors>. This is only
 * called if <validNeighbors> is a non-empty array.
 */
mxMultiplicity.prototype.checkNeighbors = function (graph, edge, source, target) {
  var sourceValue = graph.model.getValue(source);
  var targetValue = graph.model.getValue(target);
  var isValid = !this.validNeighborsAllowed;
  var valid = this.validNeighbors;

  for (var j = 0; j < valid.length; j++) {
    if (this.source && this.checkType(graph, targetValue, valid[j])) {
      isValid = this.validNeighborsAllowed;
      break;
    } else if (!this.source && this.checkType(graph, sourceValue, valid[j])) {
      isValid = this.validNeighborsAllowed;
      break;
    }
  }

  return isValid;
};

/**
 * Function: checkTerminal
 *
 * Checks the given terminal cell and returns true if this rule applies. The
 * given cell is the source or target of the given edge, depending on
 * <source>. This implementation uses <checkType> on the terminal's value.
 */
mxMultiplicity.prototype.checkTerminal = function (graph, terminal, edge) {
  var value = graph.model.getValue(terminal);

  return this.checkType(graph, value, this.type, this.attr, this.value);
};

/**
 * Function: checkType
 *
 * Checks the type of the given value.
 */
mxMultiplicity.prototype.checkType = function (graph, value, type, attr, attrValue) {
  if (value != null) {
    if (!isNaN(value.nodeType)) {
      // Checks if value is a DOM node
      return mxUtils.isNode(value, type, attr, attrValue);
    } else {
      return value == type;
    }
  }

  return false;
};

/**
 * view/mxLayoutManager.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxLayoutManager
 *
 * Implements a layout manager that runs a given layout after any changes to the graph:
 *
 * Example:
 *
 * (code)
 * var layoutMgr = new mxLayoutManager(graph);
 * layoutMgr.getLayout = function(cell, eventName)
 * {
 *   return layout;
 * };
 * (end)
 *
 * See <getLayout> for a description of the possible eventNames.
 *
 * Event: mxEvent.LAYOUT_CELLS
 *
 * Fires between begin- and endUpdate after all cells have been layouted in
 * <layoutCells>. The <code>cells</code> property contains all cells that have
 * been passed to <layoutCells>.
 *
 * Constructor: mxLayoutManager
 *
 * Constructs a new automatic layout for the given graph.
 *
 * Arguments:
 *
 * graph - Reference to the enclosing graph.
 */
function mxLayoutManager(graph) {
  // Executes the layout before the changes are dispatched
  this.undoHandler = mxUtils.bind(this, function (sender, evt) {
    if (this.isEnabled()) {
      this.beforeUndo(evt.getProperty('edit'));
    }
  });

  // Notifies the layout of a move operation inside a parent
  this.moveHandler = mxUtils.bind(this, function (sender, evt) {
    if (this.isEnabled()) {
      this.cellsMoved(evt.getProperty('cells'), evt.getProperty('event'));
    }
  });

  // Notifies the layout of a move operation inside a parent
  this.resizeHandler = mxUtils.bind(this, function (sender, evt) {
    if (this.isEnabled()) {
      this.cellsResized(evt.getProperty('cells'), evt.getProperty('bounds'), evt.getProperty('previous'));
    }
  });

  this.setGraph(graph);
}

/**
 * Extends mxEventSource.
 */
mxLayoutManager.prototype = new mxEventSource();
mxLayoutManager.prototype.constructor = mxLayoutManager;

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxLayoutManager.prototype.graph = null;

/**
 * Variable: bubbling
 *
 * Specifies if the layout should bubble along
 * the cell hierarchy. Default is true.
 */
mxLayoutManager.prototype.bubbling = true;

/**
 * Variable: enabled
 *
 * Specifies if event handling is enabled. Default is true.
 */
mxLayoutManager.prototype.enabled = true;

/**
 * Variable: undoHandler
 *
 * Holds the function that handles the endUpdate event.
 */
mxLayoutManager.prototype.undoHandler = null;

/**
 * Variable: moveHandler
 *
 * Holds the function that handles the move event.
 */
mxLayoutManager.prototype.moveHandler = null;

/**
 * Variable: resizeHandler
 *
 * Holds the function that handles the resize event.
 */
mxLayoutManager.prototype.resizeHandler = null;

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxLayoutManager.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 *
 * Parameters:
 *
 * enabled - Boolean that specifies the new enabled state.
 */
mxLayoutManager.prototype.setEnabled = function (enabled) {
  this.enabled = enabled;
};

/**
 * Function: isBubbling
 *
 * Returns true if a layout should bubble, that is, if the parent layout
 * should be executed whenever a cell layout (layout of the children of
 * a cell) has been executed. This implementation returns <bubbling>.
 */
mxLayoutManager.prototype.isBubbling = function () {
  return this.bubbling;
};

/**
 * Function: setBubbling
 *
 * Sets <bubbling>.
 */
mxLayoutManager.prototype.setBubbling = function (value) {
  this.bubbling = value;
};

/**
 * Function: getGraph
 *
 * Returns the graph that this layout operates on.
 */
mxLayoutManager.prototype.getGraph = function () {
  return this.graph;
};

/**
 * Function: setGraph
 *
 * Sets the graph that the layouts operate on.
 */
mxLayoutManager.prototype.setGraph = function (graph) {
  if (this.graph != null) {
    var model = this.graph.getModel();
    model.removeListener(this.undoHandler);
    this.graph.removeListener(this.moveHandler);
    this.graph.removeListener(this.resizeHandler);
  }

  this.graph = graph;

  if (this.graph != null) {
    var model = this.graph.getModel();
    model.addListener(mxEvent.BEFORE_UNDO, this.undoHandler);
    this.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler);
    this.graph.addListener(mxEvent.RESIZE_CELLS, this.resizeHandler);
  }
};

/**
 * Function: hasLayout
 *
 * Returns true if the given cell has a layout. This implementation invokes
 * <getLayout> with <mxEvent.LAYOUT_CELLS> as the eventName. Override this
 * if creating layouts in <getLayout> is expensive and return true if
 * <getLayout> will return a layout for the given cell for
 * <mxEvent.BEGIN_UPDATE> or <mxEvent.END_UPDATE>.
 */
mxLayoutManager.prototype.hasLayout = function (cell) {
  return this.getLayout(cell, mxEvent.LAYOUT_CELLS);
};

/**
 * Function: getLayout
 *
 * Returns the layout for the given cell and eventName. Possible
 * event names are <mxEvent.MOVE_CELLS> and <mxEvent.RESIZE_CELLS>
 * when cells are moved or resized and <mxEvent.BEGIN_UPDATE> or
 * <mxEvent.END_UPDATE> for the bottom up and top down phases after
 * changes to the graph model. <mxEvent.LAYOUT_CELLS> is used to
 * check if a layout exists for the given cell. This is called
 * from <hasLayout>.
 */
mxLayoutManager.prototype.getLayout = function (cell, eventName) {
  return null;
};

/**
 * Function: beforeUndo
 *
 * Called from <undoHandler>.
 *
 * Parameters:
 *
 * cell - Array of <mxCells> that have been moved.
 * evt - Mouse event that represents the mousedown.
 */
mxLayoutManager.prototype.beforeUndo = function (undoableEdit) {
  this.executeLayoutForCells(this.getCellsForChanges(undoableEdit.changes));
};

/**
 * Function: cellsMoved
 *
 * Called from <moveHandler>.
 *
 * Parameters:
 *
 * cell - Array of <mxCells> that have been moved.
 * evt - Mouse event that represents the mousedown.
 */
mxLayoutManager.prototype.cellsMoved = function (cells, evt) {
  if (cells != null && evt != null) {
    var point = mxUtils.convertPoint(this.getGraph().container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
    var model = this.getGraph().getModel();

    for (var i = 0; i < cells.length; i++) {
      var layout = this.getLayout(model.getParent(cells[i]), mxEvent.MOVE_CELLS);

      if (layout != null) {
        layout.moveCell(cells[i], point.x, point.y);
      }
    }
  }
};

/**
 * Function: cellsResized
 *
 * Called from <resizeHandler>.
 *
 * Parameters:
 *
 * cell - Array of <mxCells> that have been resized.
 * bounds - <mxRectangle> taht represents the new bounds.
 */
mxLayoutManager.prototype.cellsResized = function (cells, bounds, prev) {
  if (cells != null && bounds != null) {
    var model = this.getGraph().getModel();

    for (var i = 0; i < cells.length; i++) {
      var layout = this.getLayout(model.getParent(cells[i]), mxEvent.RESIZE_CELLS);

      if (layout != null) {
        layout.resizeCell(cells[i], bounds[i], prev[i]);
      }
    }
  }
};

/**
 * Function: getCellsForChanges
 *
 * Returns the cells for which a layout should be executed.
 */
mxLayoutManager.prototype.getCellsForChanges = function (changes) {
  var result = [];

  for (var i = 0; i < changes.length; i++) {
    var change = changes[i];

    if (change instanceof mxRootChange) {
      return [];
    } else {
      result = result.concat(this.getCellsForChange(change));
    }
  }

  return result;
};

/**
 * Function: getCellsForChange
 *
 * Executes all layouts which have been scheduled during the
 * changes.
 */
mxLayoutManager.prototype.getCellsForChange = function (change) {
  if (change instanceof mxChildChange) {
    return this.addCellsWithLayout(change.child, this.addCellsWithLayout(change.previous));
  } else if (change instanceof mxTerminalChange || change instanceof mxGeometryChange) {
    return this.addCellsWithLayout(change.cell);
  } else if (change instanceof mxVisibleChange || change instanceof mxStyleChange) {
    return this.addCellsWithLayout(change.cell);
  }

  return [];
};

/**
 * Function: addCellsWithLayout
 *
 * Adds all ancestors of the given cell that have a layout.
 */
mxLayoutManager.prototype.addCellsWithLayout = function (cell, result) {
  return this.addDescendantsWithLayout(cell, this.addAncestorsWithLayout(cell, result));
};

/**
 * Function: addAncestorsWithLayout
 *
 * Adds all ancestors of the given cell that have a layout.
 */
mxLayoutManager.prototype.addAncestorsWithLayout = function (cell, result) {
  result = result != null ? result : [];

  if (cell != null) {
    var layout = this.hasLayout(cell);

    if (layout != null) {
      result.push(cell);
    }

    if (this.isBubbling()) {
      var model = this.getGraph().getModel();
      this.addAncestorsWithLayout(model.getParent(cell), result);
    }
  }

  return result;
};

/**
 * Function: addDescendantsWithLayout
 *
 * Adds all descendants of the given cell that have a layout.
 */
mxLayoutManager.prototype.addDescendantsWithLayout = function (cell, result) {
  result = result != null ? result : [];

  if (cell != null && this.hasLayout(cell)) {
    var model = this.getGraph().getModel();

    for (var i = 0; i < model.getChildCount(cell); i++) {
      var child = model.getChildAt(cell, i);

      if (this.hasLayout(child)) {
        result.push(child);
        this.addDescendantsWithLayout(child, result);
      }
    }
  }

  return result;
};

/**
 * Function: executeLayoutForCells
 *
 * Executes all layouts for the given cells in two phases: In the first phase
 * layouts for child cells are executed before layouts for parent cells with
 * <mxEvent.BEGIN_UPDATE>, in the second phase layouts for parent cells are
 * executed before layouts for child cells with <mxEvent.END_UPDATE>.
 */
mxLayoutManager.prototype.executeLayoutForCells = function (cells) {
  var sorted = mxUtils.sortCells(cells, false);
  this.layoutCells(sorted, true);
  this.layoutCells(sorted.reverse(), false);
};

/**
 * Function: layoutCells
 *
 * Executes all layouts which have been scheduled during the changes.
 */
mxLayoutManager.prototype.layoutCells = function (cells, bubble) {
  if (cells.length > 0) {
    // Invokes the layouts while removing duplicates
    var model = this.getGraph().getModel();

    model.beginUpdate();
    try {
      var last = null;

      for (var i = 0; i < cells.length; i++) {
        if (cells[i] != model.getRoot() && cells[i] != last) {
          this.executeLayout(cells[i], bubble);
          last = cells[i];
        }
      }

      this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells));
    } finally {
      model.endUpdate();
    }
  }
};

/**
 * Function: executeLayout
 *
 * Executes the given layout on the given parent.
 */
mxLayoutManager.prototype.executeLayout = function (cell, bubble) {
  var layout = this.getLayout(cell, bubble ? mxEvent.BEGIN_UPDATE : mxEvent.END_UPDATE);

  if (layout != null) {
    layout.execute(cell);
  }
};

/**
 * Function: destroy
 *
 * Removes all handlers from the <graph> and deletes the reference to it.
 */
mxLayoutManager.prototype.destroy = function () {
  this.setGraph(null);
};

/**
 * view/mxSwimlaneManager.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxSwimlaneManager
 *
 * Manager for swimlanes and nested swimlanes that sets the size of newly added
 * swimlanes to that of their siblings, and propagates changes to the size of a
 * swimlane to its siblings, if <siblings> is true, and its ancestors, if
 * <bubbling> is true.
 *
 * Constructor: mxSwimlaneManager
 *
 * Constructs a new swimlane manager for the given graph.
 *
 * Arguments:
 *
 * graph - Reference to the enclosing graph.
 */
function mxSwimlaneManager(graph, horizontal, addEnabled, resizeEnabled) {
  this.horizontal = horizontal != null ? horizontal : true;
  this.addEnabled = addEnabled != null ? addEnabled : true;
  this.resizeEnabled = resizeEnabled != null ? resizeEnabled : true;

  this.addHandler = mxUtils.bind(this, function (sender, evt) {
    if (this.isEnabled() && this.isAddEnabled()) {
      this.cellsAdded(evt.getProperty('cells'));
    }
  });

  this.resizeHandler = mxUtils.bind(this, function (sender, evt) {
    if (this.isEnabled() && this.isResizeEnabled()) {
      this.cellsResized(evt.getProperty('cells'));
    }
  });

  this.setGraph(graph);
}

/**
 * Extends mxEventSource.
 */
mxSwimlaneManager.prototype = new mxEventSource();
mxSwimlaneManager.prototype.constructor = mxSwimlaneManager;

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxSwimlaneManager.prototype.graph = null;

/**
 * Variable: enabled
 *
 * Specifies if event handling is enabled. Default is true.
 */
mxSwimlaneManager.prototype.enabled = true;

/**
 * Variable: horizontal
 *
 * Specifies the orientation of the swimlanes. Default is true.
 */
mxSwimlaneManager.prototype.horizontal = true;

/**
 * Variable: addEnabled
 *
 * Specifies if newly added cells should be resized to match the size of their
 * existing siblings. Default is true.
 */
mxSwimlaneManager.prototype.addEnabled = true;

/**
 * Variable: resizeEnabled
 *
 * Specifies if resizing of swimlanes should be handled. Default is true.
 */
mxSwimlaneManager.prototype.resizeEnabled = true;

/**
 * Variable: moveHandler
 *
 * Holds the function that handles the move event.
 */
mxSwimlaneManager.prototype.addHandler = null;

/**
 * Variable: moveHandler
 *
 * Holds the function that handles the move event.
 */
mxSwimlaneManager.prototype.resizeHandler = null;

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxSwimlaneManager.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 *
 * Parameters:
 *
 * enabled - Boolean that specifies the new enabled state.
 */
mxSwimlaneManager.prototype.setEnabled = function (value) {
  this.enabled = value;
};

/**
 * Function: isHorizontal
 *
 * Returns <horizontal>.
 */
mxSwimlaneManager.prototype.isHorizontal = function () {
  return this.horizontal;
};

/**
 * Function: setHorizontal
 *
 * Sets <horizontal>.
 */
mxSwimlaneManager.prototype.setHorizontal = function (value) {
  this.horizontal = value;
};

/**
 * Function: isAddEnabled
 *
 * Returns <addEnabled>.
 */
mxSwimlaneManager.prototype.isAddEnabled = function () {
  return this.addEnabled;
};

/**
 * Function: setAddEnabled
 *
 * Sets <addEnabled>.
 */
mxSwimlaneManager.prototype.setAddEnabled = function (value) {
  this.addEnabled = value;
};

/**
 * Function: isResizeEnabled
 *
 * Returns <resizeEnabled>.
 */
mxSwimlaneManager.prototype.isResizeEnabled = function () {
  return this.resizeEnabled;
};

/**
 * Function: setResizeEnabled
 *
 * Sets <resizeEnabled>.
 */
mxSwimlaneManager.prototype.setResizeEnabled = function (value) {
  this.resizeEnabled = value;
};

/**
 * Function: getGraph
 *
 * Returns the graph that this manager operates on.
 */
mxSwimlaneManager.prototype.getGraph = function () {
  return this.graph;
};

/**
 * Function: setGraph
 *
 * Sets the graph that the manager operates on.
 */
mxSwimlaneManager.prototype.setGraph = function (graph) {
  if (this.graph != null) {
    this.graph.removeListener(this.addHandler);
    this.graph.removeListener(this.resizeHandler);
  }

  this.graph = graph;

  if (this.graph != null) {
    this.graph.addListener(mxEvent.ADD_CELLS, this.addHandler);
    this.graph.addListener(mxEvent.CELLS_RESIZED, this.resizeHandler);
  }
};

/**
 * Function: isSwimlaneIgnored
 *
 * Returns true if the given swimlane should be ignored.
 */
mxSwimlaneManager.prototype.isSwimlaneIgnored = function (swimlane) {
  return !this.getGraph().isSwimlane(swimlane);
};

/**
 * Function: isCellHorizontal
 *
 * Returns true if the given cell is horizontal. If the given cell is not a
 * swimlane, then the global orientation is returned.
 */
mxSwimlaneManager.prototype.isCellHorizontal = function (cell) {
  if (this.graph.isSwimlane(cell)) {
    var style = this.graph.getCellStyle(cell);

    return mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
  }

  return !this.isHorizontal();
};

/**
 * Function: cellsAdded
 *
 * Called if any cells have been added.
 *
 * Parameters:
 *
 * cell - Array of <mxCells> that have been added.
 */
mxSwimlaneManager.prototype.cellsAdded = function (cells) {
  if (cells != null) {
    var model = this.getGraph().getModel();

    model.beginUpdate();
    try {
      for (var i = 0; i < cells.length; i++) {
        if (!this.isSwimlaneIgnored(cells[i])) {
          this.swimlaneAdded(cells[i]);
        }
      }
    } finally {
      model.endUpdate();
    }
  }
};

/**
 * Function: swimlaneAdded
 *
 * Updates the size of the given swimlane to match that of any existing
 * siblings swimlanes.
 *
 * Parameters:
 *
 * swimlane - <mxCell> that represents the new swimlane.
 */
mxSwimlaneManager.prototype.swimlaneAdded = function (swimlane) {
  var model = this.getGraph().getModel();
  var parent = model.getParent(swimlane);
  var childCount = model.getChildCount(parent);
  var geo = null;

  // Finds the first valid sibling swimlane as reference
  for (var i = 0; i < childCount; i++) {
    var child = model.getChildAt(parent, i);

    if (child != swimlane && !this.isSwimlaneIgnored(child)) {
      geo = model.getGeometry(child);

      if (geo != null) {
        break;
      }
    }
  }

  // Applies the size of the refernece to the newly added swimlane
  if (geo != null) {
    var parentHorizontal = parent != null ? this.isCellHorizontal(parent) : this.horizontal;
    this.resizeSwimlane(swimlane, geo.width, geo.height, parentHorizontal);
  }
};

/**
 * Function: cellsResized
 *
 * Called if any cells have been resizes. Calls <swimlaneResized> for all
 * swimlanes where <isSwimlaneIgnored> returns false.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose size was changed.
 */
mxSwimlaneManager.prototype.cellsResized = function (cells) {
  if (cells != null) {
    var model = this.getGraph().getModel();

    model.beginUpdate();
    try {
      // Finds the top-level swimlanes and adds offsets
      for (var i = 0; i < cells.length; i++) {
        if (!this.isSwimlaneIgnored(cells[i])) {
          var geo = model.getGeometry(cells[i]);

          if (geo != null) {
            var size = new mxRectangle(0, 0, geo.width, geo.height);
            var top = cells[i];
            var current = top;

            while (current != null) {
              top = current;
              current = model.getParent(current);
              var tmp = this.graph.isSwimlane(current) ? this.graph.getStartSize(current) : new mxRectangle();
              size.width += tmp.width;
              size.height += tmp.height;
            }

            var parentHorizontal = current != null ? this.isCellHorizontal(current) : this.horizontal;
            this.resizeSwimlane(top, size.width, size.height, parentHorizontal);
          }
        }
      }
    } finally {
      model.endUpdate();
    }
  }
};

/**
 * Function: resizeSwimlane
 *
 * Called from <cellsResized> for all swimlanes that are not ignored to update
 * the size of the siblings and the size of the parent swimlanes, recursively,
 * if <bubbling> is true.
 *
 * Parameters:
 *
 * swimlane - <mxCell> whose size has changed.
 */
mxSwimlaneManager.prototype.resizeSwimlane = function (swimlane, w, h, parentHorizontal) {
  var model = this.getGraph().getModel();

  model.beginUpdate();
  try {
    var horizontal = this.isCellHorizontal(swimlane);

    if (!this.isSwimlaneIgnored(swimlane)) {
      var geo = model.getGeometry(swimlane);

      if (geo != null) {
        if ((parentHorizontal && geo.height != h) || (!parentHorizontal && geo.width != w)) {
          geo = geo.clone();

          if (parentHorizontal) {
            geo.height = h;
          } else {
            geo.width = w;
          }

          model.setGeometry(swimlane, geo);
        }
      }
    }

    var tmp = this.graph.isSwimlane(swimlane) ? this.graph.getStartSize(swimlane) : new mxRectangle();
    w -= tmp.width;
    h -= tmp.height;

    var childCount = model.getChildCount(swimlane);

    for (var i = 0; i < childCount; i++) {
      var child = model.getChildAt(swimlane, i);
      this.resizeSwimlane(child, w, h, horizontal);
    }
  } finally {
    model.endUpdate();
  }
};

/**
 * Function: destroy
 *
 * Removes all handlers from the <graph> and deletes the reference to it.
 */
mxSwimlaneManager.prototype.destroy = function () {
  this.setGraph(null);
};

/**
 * view/mxTemporaryCellStates.js
 */
/**
 * Copyright (c) 2006-2017, JGraph Ltd
 * Copyright (c) 2006-2017, Gaudenz Alder
 */
/**
 * Class: mxTemporaryCellStates
 *
 * Creates a temporary set of cell states.
 */
function mxTemporaryCellStates(view, scale, cells, isCellVisibleFn, getLinkForCellState) {
  scale = scale != null ? scale : 1;
  this.view = view;

  // Stores the previous state
  this.oldValidateCellState = view.validateCellState;
  this.oldBounds = view.getGraphBounds();
  this.oldStates = view.getStates();
  this.oldScale = view.getScale();
  this.oldDoRedrawShape = view.graph.cellRenderer.doRedrawShape;

  var self = this;

  // Overrides doRedrawShape and paint shape to add links on shapes
  if (getLinkForCellState != null) {
    view.graph.cellRenderer.doRedrawShape = function (state) {
      var oldPaint = state.shape.paint;

      state.shape.paint = function (c) {
        var link = getLinkForCellState(state);

        if (link != null) {
          c.setLink(link);
        }

        oldPaint.apply(this, arguments);

        if (link != null) {
          c.setLink(null);
        }
      };

      self.oldDoRedrawShape.apply(view.graph.cellRenderer, arguments);
      state.shape.paint = oldPaint;
    };
  }

  // Overrides validateCellState to ignore invisible cells
  view.validateCellState = function (cell, resurse) {
    if (cell == null || isCellVisibleFn == null || isCellVisibleFn(cell)) {
      return self.oldValidateCellState.apply(view, arguments);
    }

    return null;
  };

  // Creates space for new states
  view.setStates(new mxDictionary());
  view.setScale(scale);

  if (cells != null) {
    view.resetValidationState();
    var bbox = null;

    // Validates the vertices and edges without adding them to
    // the model so that the original cells are not modified
    for (var i = 0; i < cells.length; i++) {
      var bounds = view.getBoundingBox(view.validateCellState(view.validateCell(cells[i])));

      if (bbox == null) {
        bbox = bounds;
      } else {
        bbox.add(bounds);
      }
    }

    view.setGraphBounds(bbox || new mxRectangle());
  }
}

/**
 * Variable: view
 *
 * Holds the width of the rectangle. Default is 0.
 */
mxTemporaryCellStates.prototype.view = null;

/**
 * Variable: oldStates
 *
 * Holds the height of the rectangle. Default is 0.
 */
mxTemporaryCellStates.prototype.oldStates = null;

/**
 * Variable: oldBounds
 *
 * Holds the height of the rectangle. Default is 0.
 */
mxTemporaryCellStates.prototype.oldBounds = null;

/**
 * Variable: oldScale
 *
 * Holds the height of the rectangle. Default is 0.
 */
mxTemporaryCellStates.prototype.oldScale = null;

/**
 * Function: destroy
 *
 * Returns the top, left corner as a new <mxPoint>.
 */
mxTemporaryCellStates.prototype.destroy = function () {
  this.view.setScale(this.oldScale);
  this.view.setStates(this.oldStates);
  this.view.setGraphBounds(this.oldBounds);
  this.view.validateCellState = this.oldValidateCellState;
  this.view.graph.cellRenderer.doRedrawShape = this.oldDoRedrawShape;
};

/**
 * view/mxCellStatePreview.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 *
 * Class: mxCellStatePreview
 *
 * Implements a live preview for moving cells.
 *
 * Constructor: mxCellStatePreview
 *
 * Constructs a move preview for the given graph.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 */
function mxCellStatePreview(graph) {
  this.deltas = new mxDictionary();
  this.graph = graph;
}

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxCellStatePreview.prototype.graph = null;

/**
 * Variable: deltas
 *
 * Reference to the enclosing <mxGraph>.
 */
mxCellStatePreview.prototype.deltas = null;

/**
 * Variable: count
 *
 * Contains the number of entries in the map.
 */
mxCellStatePreview.prototype.count = 0;

/**
 * Function: isEmpty
 *
 * Returns true if this contains no entries.
 */
mxCellStatePreview.prototype.isEmpty = function () {
  return this.count == 0;
};

/**
 * Function: moveState
 */
mxCellStatePreview.prototype.moveState = function (state, dx, dy, add, includeEdges) {
  add = add != null ? add : true;
  includeEdges = includeEdges != null ? includeEdges : true;

  var delta = this.deltas.get(state.cell);

  if (delta == null) {
    // Note: Deltas stores the point and the state since the key is a string.
    delta = { point: new mxPoint(dx, dy), state: state };
    this.deltas.put(state.cell, delta);
    this.count++;
  } else if (add) {
    delta.point.x += dx;
    delta.point.y += dy;
  } else {
    delta.point.x = dx;
    delta.point.y = dy;
  }

  if (includeEdges) {
    this.addEdges(state);
  }

  return delta.point;
};

/**
 * Function: show
 */
mxCellStatePreview.prototype.show = function (visitor) {
  this.deltas.visit(
    mxUtils.bind(this, function (key, delta) {
      this.translateState(delta.state, delta.point.x, delta.point.y);
    })
  );

  this.deltas.visit(
    mxUtils.bind(this, function (key, delta) {
      this.revalidateState(delta.state, delta.point.x, delta.point.y, visitor);
    })
  );
};

/**
 * Function: translateState
 */
mxCellStatePreview.prototype.translateState = function (state, dx, dy) {
  if (state != null) {
    var model = this.graph.getModel();

    if (model.isVertex(state.cell)) {
      state.view.updateCellState(state);
      var geo = model.getGeometry(state.cell);

      // Moves selection cells and non-relative vertices in
      // the first phase so that edge terminal points will
      // be updated in the second phase
      if ((dx != 0 || dy != 0) && geo != null && (!geo.relative || this.deltas.get(state.cell) != null)) {
        state.x += dx;
        state.y += dy;
      }
    }

    var childCount = model.getChildCount(state.cell);

    for (var i = 0; i < childCount; i++) {
      this.translateState(state.view.getState(model.getChildAt(state.cell, i)), dx, dy);
    }
  }
};

/**
 * Function: revalidateState
 */
mxCellStatePreview.prototype.revalidateState = function (state, dx, dy, visitor) {
  if (state != null) {
    var model = this.graph.getModel();

    // Updates the edge terminal points and restores the
    // (relative) positions of any (relative) children
    if (model.isEdge(state.cell)) {
      state.view.updateCellState(state);
    }

    var geo = this.graph.getCellGeometry(state.cell);
    var pState = state.view.getState(model.getParent(state.cell));

    // Moves selection vertices which are relative
    if (
      (dx != 0 || dy != 0) &&
      geo != null &&
      geo.relative &&
      model.isVertex(state.cell) &&
      (pState == null || model.isVertex(pState.cell) || this.deltas.get(state.cell) != null)
    ) {
      state.x += dx;
      state.y += dy;
    }

    this.graph.cellRenderer.redraw(state);

    // Invokes the visitor on the given state
    if (visitor != null) {
      visitor(state);
    }

    var childCount = model.getChildCount(state.cell);

    for (var i = 0; i < childCount; i++) {
      this.revalidateState(this.graph.view.getState(model.getChildAt(state.cell, i)), dx, dy, visitor);
    }
  }
};

/**
 * Function: addEdges
 */
mxCellStatePreview.prototype.addEdges = function (state) {
  var model = this.graph.getModel();
  var edgeCount = model.getEdgeCount(state.cell);

  for (var i = 0; i < edgeCount; i++) {
    var s = state.view.getState(model.getEdgeAt(state.cell, i));

    if (s != null) {
      this.moveState(s, 0, 0);
    }
  }
};

/**
 * view/mxConnectionConstraint.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxConnectionConstraint
 *
 * Defines an object that contains the constraints about how to connect one
 * side of an edge to its terminal.
 *
 * Constructor: mxConnectionConstraint
 *
 * Constructs a new connection constraint for the given point and boolean
 * arguments.
 *
 * Parameters:
 *
 * point - Optional <mxPoint> that specifies the fixed location of the point
 * in relative coordinates. Default is null.
 * perimeter - Optional boolean that specifies if the fixed point should be
 * projected onto the perimeter of the terminal. Default is true.
 */
function mxConnectionConstraint(point, perimeter, name, dx, dy) {
  this.point = point;
  this.perimeter = perimeter != null ? perimeter : true;
  this.name = name;
  this.dx = dx ? dx : 0;
  this.dy = dy ? dy : 0;
}

/**
 * Variable: point
 *
 * <mxPoint> that specifies the fixed location of the connection point.
 */
mxConnectionConstraint.prototype.point = null;

/**
 * Variable: perimeter
 *
 * Boolean that specifies if the point should be projected onto the perimeter
 * of the terminal.
 */
mxConnectionConstraint.prototype.perimeter = null;

/**
 * Variable: name
 *
 * Optional string that specifies the name of the constraint.
 */
mxConnectionConstraint.prototype.name = null;

/**
 * Variable: dx
 *
 * Optional float that specifies the horizontal offset of the constraint.
 */
mxConnectionConstraint.prototype.dx = null;

/**
 * Variable: dy
 *
 * Optional float that specifies the vertical offset of the constraint.
 */
mxConnectionConstraint.prototype.dy = null;

/**
 * handler/mxGraphHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGraphHandler
 *
 * Graph event handler that handles selection. Individual cells are handled
 * separately using <mxVertexHandler> or one of the edge handlers. These
 * handlers are created using <mxGraph.createHandler> in
 * <mxGraphSelectionModel.cellAdded>.
 *
 * To avoid the container to scroll a moved cell into view, set
 * <scrollAfterMove> to false.
 *
 * Constructor: mxGraphHandler
 *
 * Constructs an event handler that creates handles for the
 * selection cells.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 */
function mxGraphHandler(graph) {
  this.graph = graph;
  this.graph.addMouseListener(this);

  // Repaints the handler after autoscroll
  this.panHandler = mxUtils.bind(this, function () {
    if (!this.suspended) {
      this.updatePreview();
      this.updateHint();
    }
  });

  this.graph.addListener(mxEvent.PAN, this.panHandler);

  // Handles escape keystrokes
  this.escapeHandler = mxUtils.bind(this, function (sender, evt) {
    this.reset();
  });

  this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);

  // Updates the preview box for remote changes
  this.refreshHandler = mxUtils.bind(this, function (sender, evt) {
    // Merges multiple pending calls
    if (this.refreshThread) {
      window.clearTimeout(this.refreshThread);
    }

    // Waits for the states and handlers to be updated
    this.refreshThread = window.setTimeout(
      mxUtils.bind(this, function () {
        this.refreshThread = null;

        if (this.first != null && !this.suspended) {
          // Updates preview with no translate to compute bounding box
          var dx = this.currentDx;
          var dy = this.currentDy;
          this.currentDx = 0;
          this.currentDy = 0;
          this.updatePreview();
          this.bounds = this.graph.getView().getBounds(this.cells);
          this.pBounds = this.getPreviewBounds(this.cells);

          if (this.pBounds == null && !this.livePreviewUsed) {
            this.reset();
          } else {
            // Restores translate and updates preview
            this.currentDx = dx;
            this.currentDy = dy;
            this.updatePreview();
            this.updateHint();

            if (this.livePreviewUsed) {
              // Forces update to ignore last visible state
              this.setHandlesVisibleForCells(this.graph.selectionCellsHandler.getHandledSelectionCells(), false, true);
              this.updatePreview();
            }
          }
        }
      }),
      0
    );
  });

  this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);
  this.graph.addListener(mxEvent.REFRESH, this.refreshHandler);

  this.keyHandler = mxUtils.bind(this, function (e) {
    if (
      this.graph.container != null &&
      this.graph.container.style.visibility != 'hidden' &&
      this.first != null &&
      !this.suspended
    ) {
      var clone = this.graph.isCloneEvent(e) && this.graph.isCellsCloneable() && this.isCloneEnabled();

      if (clone != this.cloning) {
        this.cloning = clone;
        this.checkPreview();
        this.updatePreview();
      }
    }
  });

  mxEvent.addListener(document, 'keydown', this.keyHandler);
  mxEvent.addListener(document, 'keyup', this.keyHandler);
}

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxGraphHandler.prototype.graph = null;

/**
 * Variable: maxCells
 *
 * Defines the maximum number of cells to paint subhandles
 * for. Default is 50 for Firefox and 20 for IE. Set this
 * to 0 if you want an unlimited number of handles to be
 * displayed. This is only recommended if the number of
 * cells in the graph is limited to a small number, eg.
 * 500.
 */
mxGraphHandler.prototype.maxCells = mxClient.IS_IE ? 20 : 50;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxGraphHandler.prototype.enabled = true;

/**
 * Variable: highlightEnabled
 *
 * Specifies if drop targets under the mouse should be enabled. Default is
 * true.
 */
mxGraphHandler.prototype.highlightEnabled = true;

/**
 * Variable: cloneEnabled
 *
 * Specifies if cloning by control-drag is enabled. Default is true.
 */
mxGraphHandler.prototype.cloneEnabled = true;

/**
 * Variable: moveEnabled
 *
 * Specifies if moving is enabled. Default is true.
 */
mxGraphHandler.prototype.moveEnabled = true;

/**
 * Variable: guidesEnabled
 *
 * Specifies if other cells should be used for snapping the right, center or
 * left side of the current selection. Default is false.
 */
mxGraphHandler.prototype.guidesEnabled = false;

/**
 * Variable: handlesVisible
 *
 * Whether the handles of the selection are currently visible.
 */
mxGraphHandler.prototype.handlesVisible = true;

/**
 * Variable: guide
 *
 * Holds the <mxGuide> instance that is used for alignment.
 */
mxGraphHandler.prototype.guide = null;

/**
 * Variable: currentDx
 *
 * Stores the x-coordinate of the current mouse move.
 */
mxGraphHandler.prototype.currentDx = null;

/**
 * Variable: currentDy
 *
 * Stores the y-coordinate of the current mouse move.
 */
mxGraphHandler.prototype.currentDy = null;

/**
 * Variable: updateCursor
 *
 * Specifies if a move cursor should be shown if the mouse is over a movable
 * cell. Default is true.
 */
mxGraphHandler.prototype.updateCursor = true;

/**
 * Variable: selectEnabled
 *
 * Specifies if selecting is enabled. Default is true.
 */
mxGraphHandler.prototype.selectEnabled = true;

/**
 * Variable: removeCellsFromParent
 *
 * Specifies if cells may be moved out of their parents. Default is true.
 */
mxGraphHandler.prototype.removeCellsFromParent = true;

/**
 * Variable: removeEmptyParents
 *
 * If empty parents should be removed from the model after all child cells
 * have been moved out. Default is true.
 */
mxGraphHandler.prototype.removeEmptyParents = false;

/**
 * Variable: connectOnDrop
 *
 * Specifies if drop events are interpreted as new connections if no other
 * drop action is defined. Default is false.
 */
mxGraphHandler.prototype.connectOnDrop = false;

/**
 * Variable: scrollOnMove
 *
 * Specifies if the view should be scrolled so that a moved cell is
 * visible. Default is true.
 */
mxGraphHandler.prototype.scrollOnMove = true;

/**
 * Variable: minimumSize
 *
 * Specifies the minimum number of pixels for the width and height of a
 * selection border. Default is 6.
 */
mxGraphHandler.prototype.minimumSize = 6;

/**
 * Variable: previewColor
 *
 * Specifies the color of the preview shape. Default is black.
 */
mxGraphHandler.prototype.previewColor = 'black';

/**
 * Variable: htmlPreview
 *
 * Specifies if the graph container should be used for preview. If this is used
 * then drop target detection relies entirely on <mxGraph.getCellAt> because
 * the HTML preview does not "let events through". Default is false.
 */
mxGraphHandler.prototype.htmlPreview = false;

/**
 * Variable: shape
 *
 * Reference to the <mxShape> that represents the preview.
 */
mxGraphHandler.prototype.shape = null;

/**
 * Variable: scaleGrid
 *
 * Specifies if the grid should be scaled. Default is false.
 */
mxGraphHandler.prototype.scaleGrid = false;

/**
 * Variable: rotationEnabled
 *
 * Specifies if the bounding box should allow for rotation. Default is true.
 */
mxGraphHandler.prototype.rotationEnabled = true;

/**
 * Variable: maxLivePreview
 *
 * Maximum number of cells for which live preview should be used. Default is 0
 * which means no live preview.
 */
mxGraphHandler.prototype.maxLivePreview = 0;

/**
 * Variable: allowLivePreview
 *
 * If live preview is allowed on this system. Default is true for systems with
 * SVG support.
 */
mxGraphHandler.prototype.allowLivePreview = mxClient.IS_SVG;

/**
 * Function: isEnabled
 *
 * Returns <enabled>.
 */
mxGraphHandler.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Sets <enabled>.
 */
mxGraphHandler.prototype.setEnabled = function (value) {
  this.enabled = value;
};

/**
 * Function: isCloneEnabled
 *
 * Returns <cloneEnabled>.
 */
mxGraphHandler.prototype.isCloneEnabled = function () {
  return this.cloneEnabled;
};

/**
 * Function: setCloneEnabled
 *
 * Sets <cloneEnabled>.
 *
 * Parameters:
 *
 * value - Boolean that specifies the new clone enabled state.
 */
mxGraphHandler.prototype.setCloneEnabled = function (value) {
  this.cloneEnabled = value;
};

/**
 * Function: isMoveEnabled
 *
 * Returns <moveEnabled>.
 */
mxGraphHandler.prototype.isMoveEnabled = function () {
  return this.moveEnabled;
};

/**
 * Function: setMoveEnabled
 *
 * Sets <moveEnabled>.
 */
mxGraphHandler.prototype.setMoveEnabled = function (value) {
  this.moveEnabled = value;
};

/**
 * Function: isSelectEnabled
 *
 * Returns <selectEnabled>.
 */
mxGraphHandler.prototype.isSelectEnabled = function () {
  return this.selectEnabled;
};

/**
 * Function: setSelectEnabled
 *
 * Sets <selectEnabled>.
 */
mxGraphHandler.prototype.setSelectEnabled = function (value) {
  this.selectEnabled = value;
};

/**
 * Function: isRemoveCellsFromParent
 *
 * Returns <removeCellsFromParent>.
 */
mxGraphHandler.prototype.isRemoveCellsFromParent = function () {
  return this.removeCellsFromParent;
};

/**
 * Function: setRemoveCellsFromParent
 *
 * Sets <removeCellsFromParent>.
 */
mxGraphHandler.prototype.setRemoveCellsFromParent = function (value) {
  this.removeCellsFromParent = value;
};

/**
 * Function: isPropagateSelectionCell
 *
 * Returns true if the given cell and parent should propagate
 * selection state to the parent.
 */
mxGraphHandler.prototype.isPropagateSelectionCell = function (cell, immediate, me) {
  var parent = this.graph.model.getParent(cell);

  if (immediate) {
    var geo = this.graph.model.isEdge(cell) ? null : this.graph.getCellGeometry(cell);

    return !this.graph.isSiblingSelected(cell) && ((geo != null && geo.relative) || !this.graph.isSwimlane(parent));
  } else {
    return (
      (!this.graph.isToggleEvent(me.getEvent()) ||
        (!this.graph.isSiblingSelected(cell) && !this.graph.isCellSelected(cell) && !this.graph.isSwimlane(parent)) ||
        this.graph.isCellSelected(parent)) &&
      (this.graph.isToggleEvent(me.getEvent()) || !this.graph.isCellSelected(parent))
    );
  }
};

/**
 * Function: getInitialCellForEvent
 *
 * Hook to return initial cell for the given event. This returns
 * the topmost cell that is not a swimlane or is selected.
 */
mxGraphHandler.prototype.getInitialCellForEvent = function (me) {
  var state = me.getState();

  if (
    (!this.graph.isToggleEvent(me.getEvent()) || !mxEvent.isAltDown(me.getEvent())) &&
    state != null &&
    !this.graph.isCellSelected(state.cell)
  ) {
    var model = this.graph.model;
    var next = this.graph.view.getState(model.getParent(state.cell));

    while (
      next != null &&
      !this.graph.isCellSelected(next.cell) &&
      (model.isVertex(next.cell) || model.isEdge(next.cell)) &&
      this.isPropagateSelectionCell(state.cell, true, me)
    ) {
      state = next;
      next = this.graph.view.getState(this.graph.getModel().getParent(state.cell));
    }
  }

  return state != null ? state.cell : null;
};

/**
 * Function: isDelayedSelection
 *
 * Returns true if the cell or one of its ancestors is selected.
 */
mxGraphHandler.prototype.isDelayedSelection = function (cell, me) {
  if (!this.graph.isToggleEvent(me.getEvent()) || !mxEvent.isAltDown(me.getEvent())) {
    while (cell != null) {
      if (this.graph.selectionCellsHandler.isHandled(cell)) {
        return this.graph.cellEditor.getEditingCell() != cell;
      }

      cell = this.graph.model.getParent(cell);
    }
  }

  return this.graph.isToggleEvent(me.getEvent()) && !mxEvent.isAltDown(me.getEvent());
};

/**
 * Function: selectDelayed
 *
 * Implements the delayed selection for the given mouse event.
 */
mxGraphHandler.prototype.selectDelayed = function (me) {
  if (!this.graph.popupMenuHandler.isPopupTrigger(me)) {
    var cell = me.getCell();

    if (cell == null) {
      cell = this.cell;
    }

    this.selectCellForEvent(cell, me);
  }
};

/**
 * Function: selectCellForEvent
 *
 * Selects the given cell for the given <mxMouseEvent>.
 */
mxGraphHandler.prototype.selectCellForEvent = function (cell, me) {
  var state = this.graph.view.getState(cell);

  if (state != null) {
    if (me.isSource(state.control)) {
      this.graph.selectCellForEvent(cell, me.getEvent());
    } else {
      if (!this.graph.isToggleEvent(me.getEvent()) || !mxEvent.isAltDown(me.getEvent())) {
        var model = this.graph.getModel();
        var parent = model.getParent(cell);

        while (
          this.graph.view.getState(parent) != null &&
          (model.isVertex(parent) || model.isEdge(parent)) &&
          this.isPropagateSelectionCell(cell, false, me)
        ) {
          cell = parent;
          parent = model.getParent(cell);
        }
      }

      this.graph.selectCellForEvent(cell, me.getEvent());
    }
  }

  return cell;
};

/**
 * Function: consumeMouseEvent
 *
 * Consumes the given mouse event. NOTE: This may be used to enable click
 * events for links in labels on iOS as follows as consuming the initial
 * touchStart disables firing the subsequent click event on the link.
 *
 * <code>
 * mxGraphHandler.prototype.consumeMouseEvent = function(evtName, me)
 * {
 *   var source = mxEvent.getSource(me.getEvent());
 *
 *   if (!mxEvent.isTouchEvent(me.getEvent()) || source.nodeName != 'A')
 *   {
 *     me.consume();
 *   }
 * }
 * </code>
 */
mxGraphHandler.prototype.consumeMouseEvent = function (evtName, me) {
  me.consume();
};

/**
 * Function: mouseDown
 *
 * Handles the event by selecing the given cell and creating a handle for
 * it. By consuming the event all subsequent events of the gesture are
 * redirected to this handler.
 */
mxGraphHandler.prototype.mouseDown = function (sender, me) {
  if (
    !me.isConsumed() &&
    this.isEnabled() &&
    this.graph.isEnabled() &&
    me.getState() != null &&
    !mxEvent.isMultiTouchEvent(me.getEvent())
  ) {
    var cell = this.getInitialCellForEvent(me);
    this.delayedSelection = this.isDelayedSelection(cell, me);
    this.cell = null;

    if (this.isSelectEnabled() && !this.delayedSelection) {
      this.graph.selectCellForEvent(cell, me.getEvent());
    }

    if (this.isMoveEnabled()) {
      var model = this.graph.model;
      var geo = model.getGeometry(cell);

      if (
        this.graph.isCellMovable(cell) &&
        (!model.isEdge(cell) ||
          this.graph.getSelectionCount() > 1 ||
          (geo.points != null && geo.points.length > 0) ||
          model.getTerminal(cell, true) == null ||
          model.getTerminal(cell, false) == null ||
          this.graph.allowDanglingEdges ||
          (this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable()))
      ) {
        this.start(cell, me.getX(), me.getY());
      } else if (this.delayedSelection) {
        this.cell = cell;
      }

      this.cellWasClicked = true;
      this.consumeMouseEvent(mxEvent.MOUSE_DOWN, me);
    }
  }
};

/**
 * Function: getGuideStates
 *
 * Creates an array of cell states which should be used as guides.
 */
mxGraphHandler.prototype.getGuideStates = function () {
  var parent = this.graph.getDefaultParent();
  var model = this.graph.getModel();

  var filter = mxUtils.bind(this, function (cell) {
    return (
      this.graph.view.getState(cell) != null &&
      model.isVertex(cell) &&
      model.getGeometry(cell) != null &&
      !model.getGeometry(cell).relative
    );
  });

  return this.graph.view.getCellStates(model.filterDescendants(filter, parent));
};

/**
 * Function: getCells
 *
 * Returns the cells to be modified by this handler. This implementation
 * returns all selection cells that are movable, or the given initial cell if
 * the given cell is not selected and movable. This handles the case of moving
 * unselectable or unselected cells.
 *
 * Parameters:
 *
 * initialCell - <mxCell> that triggered this handler.
 */
mxGraphHandler.prototype.getCells = function (initialCell) {
  if (!this.delayedSelection && this.graph.isCellMovable(initialCell)) {
    return [initialCell];
  } else {
    return this.graph.getMovableCells(this.graph.getSelectionCells());
  }
};

/**
 * Function: getPreviewBounds
 *
 * Returns the <mxRectangle> used as the preview bounds for
 * moving the given cells.
 */
mxGraphHandler.prototype.getPreviewBounds = function (cells) {
  var bounds = this.getBoundingBox(cells);

  if (bounds != null) {
    // Corrects width and height
    bounds.width = Math.max(0, bounds.width - 1);
    bounds.height = Math.max(0, bounds.height - 1);

    if (bounds.width < this.minimumSize) {
      var dx = this.minimumSize - bounds.width;
      bounds.x -= dx / 2;
      bounds.width = this.minimumSize;
    } else {
      bounds.x = Math.round(bounds.x);
      bounds.width = Math.ceil(bounds.width);
    }

    var tr = this.graph.view.translate;
    var s = this.graph.view.scale;

    if (bounds.height < this.minimumSize) {
      var dy = this.minimumSize - bounds.height;
      bounds.y -= dy / 2;
      bounds.height = this.minimumSize;
    } else {
      bounds.y = Math.round(bounds.y);
      bounds.height = Math.ceil(bounds.height);
    }
  }

  return bounds;
};

/**
 * Function: getBoundingBox
 *
 * Returns the union of the <mxCellStates> for the given array of <mxCells>.
 * For vertices, this method uses the bounding box of the corresponding shape
 * if one exists. The bounding box of the corresponding text label and all
 * controls and overlays are ignored. See also: <mxGraphView.getBounds> and
 * <mxGraph.getBoundingBox>.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose bounding box should be returned.
 */
mxGraphHandler.prototype.getBoundingBox = function (cells) {
  var result = null;

  if (cells != null && cells.length > 0) {
    var model = this.graph.getModel();

    for (var i = 0; i < cells.length; i++) {
      if (model.isVertex(cells[i]) || model.isEdge(cells[i])) {
        var state = this.graph.view.getState(cells[i]);

        if (state != null) {
          var bbox = state;

          if (model.isVertex(cells[i]) && state.shape != null && state.shape.boundingBox != null) {
            bbox = state.shape.boundingBox;
          }

          if (result == null) {
            result = mxRectangle.fromRectangle(bbox);
          } else {
            result.add(bbox);
          }
        }
      }
    }
  }

  return result;
};

/**
 * Function: createPreviewShape
 *
 * Creates the shape used to draw the preview for the given bounds.
 */
mxGraphHandler.prototype.createPreviewShape = function (bounds) {
  var shape = new mxRectangleShape(bounds, null, this.previewColor);
  shape.isDashed = true;

  if (this.htmlPreview) {
    shape.dialect = mxConstants.DIALECT_STRICTHTML;
    shape.init(this.graph.container);
  } else {
    // Makes sure to use either VML or SVG shapes in order to implement
    // event-transparency on the background area of the rectangle since
    // HTML shapes do not let mouseevents through even when transparent
    shape.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
    shape.init(this.graph.getView().getOverlayPane());
    shape.pointerEvents = false;

    // Workaround for artifacts on iOS
    if (mxClient.IS_IOS) {
      shape.getSvgScreenOffset = function () {
        return 0;
      };
    }
  }

  return shape;
};

/**
 * Function: start
 *
 * Starts the handling of the mouse gesture.
 */
mxGraphHandler.prototype.start = function (cell, x, y, cells) {
  this.cell = cell;
  this.first = mxUtils.convertPoint(this.graph.container, x, y);
  this.cells = cells != null ? cells : this.getCells(this.cell);
  this.bounds = this.graph.getView().getBounds(this.cells);
  this.pBounds = this.getPreviewBounds(this.cells);
  this.allCells = new mxDictionary();
  this.cloning = false;
  this.cellCount = 0;

  for (var i = 0; i < this.cells.length; i++) {
    this.cellCount += this.addStates(this.cells[i], this.allCells);
  }

  if (this.guidesEnabled) {
    this.guide = new mxGuide(this.graph, this.getGuideStates());
    var parent = this.graph.model.getParent(cell);
    var ignore = this.graph.model.getChildCount(parent) < 2;

    // Uses connected states as guides
    var connected = new mxDictionary();
    var opps = this.graph.getOpposites(this.graph.getEdges(this.cell), this.cell);

    for (var i = 0; i < opps.length; i++) {
      var state = this.graph.view.getState(opps[i]);

      if (state != null && !connected.get(state)) {
        connected.put(state, true);
      }
    }

    this.guide.isStateIgnored = mxUtils.bind(this, function (state) {
      var p = this.graph.model.getParent(state.cell);

      return (
        state.cell != null &&
        ((!this.cloning && this.isCellMoving(state.cell)) ||
          (state.cell != (this.target || parent) &&
            !ignore &&
            !connected.get(state) &&
            (this.target == null || this.graph.model.getChildCount(this.target) >= 2) &&
            p != (this.target || parent)))
      );
    });
  }
};

/**
 * Function: addStates
 *
 * Adds the states for the given cell recursively to the given dictionary.
 */
mxGraphHandler.prototype.addStates = function (cell, dict) {
  var state = this.graph.view.getState(cell);
  var count = 0;

  if (state != null && dict.get(cell) == null) {
    dict.put(cell, state);
    count++;

    var childCount = this.graph.model.getChildCount(cell);

    for (var i = 0; i < childCount; i++) {
      count += this.addStates(this.graph.model.getChildAt(cell, i), dict);
    }
  }

  return count;
};

/**
 * Function: isCellMoving
 *
 * Returns true if the given cell is currently being moved.
 */
mxGraphHandler.prototype.isCellMoving = function (cell) {
  return this.allCells.get(cell) != null;
};

/**
 * Function: useGuidesForEvent
 *
 * Returns true if the guides should be used for the given <mxMouseEvent>.
 * This implementation returns <mxGuide.isEnabledForEvent>.
 */
mxGraphHandler.prototype.useGuidesForEvent = function (me) {
  return this.guide != null
    ? this.guide.isEnabledForEvent(me.getEvent()) && !this.graph.isConstrainedEvent(me.getEvent())
    : true;
};

/**
 * Function: snap
 *
 * Snaps the given vector to the grid and returns the given mxPoint instance.
 */
mxGraphHandler.prototype.snap = function (vector) {
  var scale = this.scaleGrid ? this.graph.view.scale : 1;

  vector.x = this.graph.snap(vector.x / scale) * scale;
  vector.y = this.graph.snap(vector.y / scale) * scale;

  return vector;
};

/**
 * Function: getDelta
 *
 * Returns an <mxPoint> that represents the vector for moving the cells
 * for the given <mxMouseEvent>.
 */
mxGraphHandler.prototype.getDelta = function (me) {
  var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());

  return new mxPoint(point.x - this.first.x - this.graph.panDx, point.y - this.first.y - this.graph.panDy);
};

/**
 * Function: updateHint
 *
 * Hook for subclassers do show details while the handler is active.
 */
mxGraphHandler.prototype.updateHint = function (me) {};

/**
 * Function: removeHint
 *
 * Hooks for subclassers to hide details when the handler gets inactive.
 */
mxGraphHandler.prototype.removeHint = function () {};

/**
 * Function: roundLength
 *
 * Hook for rounding the unscaled vector. Allows for half steps in the raster so
 * numbers coming in should be rounded if no half steps are allowed (ie for non
 * aligned standard moving where pixel steps should be preferred).
 */
mxGraphHandler.prototype.roundLength = function (length) {
  return Math.round(length * 100) / 100;
};

/**
 * Function: isValidDropTarget
 *
 * Returns true if the given cell is a valid drop target.
 */
mxGraphHandler.prototype.isValidDropTarget = function (target, me) {
  return this.graph.model.getParent(this.cell) != target;
};

/**
 * Function: checkPreview
 *
 * Updates the preview if cloning state has changed.
 */
mxGraphHandler.prototype.checkPreview = function () {
  if (this.livePreviewActive && this.cloning) {
    this.resetLivePreview();
    this.livePreviewActive = false;
  } else if (this.maxLivePreview >= this.cellCount && !this.livePreviewActive && this.allowLivePreview) {
    if (!this.cloning || !this.livePreviewActive) {
      this.livePreviewActive = true;
      this.livePreviewUsed = true;
    }
  } else if (!this.livePreviewUsed && this.shape == null) {
    this.shape = this.createPreviewShape(this.bounds);
  }
};

/**
 * Function: mouseMove
 *
 * Handles the event by highlighting possible drop targets and updating the
 * preview.
 */
mxGraphHandler.prototype.mouseMove = function (sender, me) {
  var graph = this.graph;

  if (
    !me.isConsumed() &&
    graph.isMouseDown &&
    this.cell != null &&
    this.first != null &&
    this.bounds != null &&
    !this.suspended
  ) {
    // Stops moving if a multi touch event is received
    if (mxEvent.isMultiTouchEvent(me.getEvent())) {
      this.reset();
      return;
    }

    var delta = this.getDelta(me);
    var tol = graph.tolerance;

    if (this.shape != null || this.livePreviewActive || Math.abs(delta.x) > tol || Math.abs(delta.y) > tol) {
      // Highlight is used for highlighting drop targets
      if (this.highlight == null) {
        this.highlight = new mxCellHighlight(this.graph, mxConstants.DROP_TARGET_COLOR, 3);
      }

      var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
      var gridEnabled = graph.isGridEnabledEvent(me.getEvent());
      var cell = me.getCell();
      var hideGuide = true;
      var target = null;
      this.cloning = clone;

      if (graph.isDropEnabled() && this.highlightEnabled) {
        // Contains a call to getCellAt to find the cell under the mouse
        target = graph.getDropTarget(this.cells, me.getEvent(), cell, clone);
      }

      var state = graph.getView().getState(target);
      var highlight = false;

      if (state != null && (clone || this.isValidDropTarget(target, me))) {
        if (this.target != target) {
          this.target = target;
          this.setHighlightColor(mxConstants.DROP_TARGET_COLOR);
        }

        highlight = true;
      } else {
        this.target = null;

        if (
          this.connectOnDrop &&
          cell != null &&
          this.cells.length == 1 &&
          graph.getModel().isVertex(cell) &&
          graph.isCellConnectable(cell)
        ) {
          state = graph.getView().getState(cell);

          if (state != null) {
            var error = graph.getEdgeValidationError(null, this.cell, cell);
            var color = error == null ? mxConstants.VALID_COLOR : mxConstants.INVALID_CONNECT_TARGET_COLOR;
            this.setHighlightColor(color);
            highlight = true;
          }
        }
      }

      if (state != null && highlight) {
        this.highlight.highlight(state);
      } else {
        this.highlight.hide();
      }

      if (this.guide != null && this.useGuidesForEvent(me)) {
        delta = this.guide.move(this.bounds, delta, gridEnabled, clone);
        hideGuide = false;
      } else {
        delta = this.graph.snapDelta(delta, this.bounds, !gridEnabled, false, false);
      }

      if (this.guide != null && hideGuide) {
        this.guide.hide();
      }

      // Constrained movement if shift key is pressed
      if (graph.isConstrainedEvent(me.getEvent())) {
        if (Math.abs(delta.x) > Math.abs(delta.y)) {
          delta.y = 0;
        } else {
          delta.x = 0;
        }
      }

      this.checkPreview();

      if (this.currentDx != delta.x || this.currentDy != delta.y) {
        this.currentDx = delta.x;
        this.currentDy = delta.y;
        this.updatePreview();
      }
    }

    this.updateHint(me);
    this.consumeMouseEvent(mxEvent.MOUSE_MOVE, me);

    // Cancels the bubbling of events to the container so
    // that the droptarget is not reset due to an mouseMove
    // fired on the container with no associated state.
    mxEvent.consume(me.getEvent());
  } else if (
    (this.isMoveEnabled() || this.isCloneEnabled()) &&
    this.updateCursor &&
    !me.isConsumed() &&
    (me.getState() != null || me.sourceState != null) &&
    !graph.isMouseDown
  ) {
    var cursor = graph.getCursorForMouseEvent(me);

    if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell())) {
      if (graph.getModel().isEdge(me.getCell())) {
        cursor = mxConstants.CURSOR_MOVABLE_EDGE;
      } else {
        cursor = mxConstants.CURSOR_MOVABLE_VERTEX;
      }
    }

    // Sets the cursor on the original source state under the mouse
    // instead of the event source state which can be the parent
    if (cursor != null && me.sourceState != null) {
      me.sourceState.setCursor(cursor);
    }
  }
};

/**
 * Function: updatePreview
 *
 * Updates the bounds of the preview shape.
 */
mxGraphHandler.prototype.updatePreview = function (remote) {
  if (this.livePreviewUsed && !remote) {
    if (this.cells != null) {
      this.setHandlesVisibleForCells(this.graph.selectionCellsHandler.getHandledSelectionCells(), false);
      this.updateLivePreview(this.currentDx, this.currentDy);
    }
  } else {
    this.updatePreviewShape();
  }
};

/**
 * Function: updatePreviewShape
 *
 * Updates the bounds of the preview shape.
 */
mxGraphHandler.prototype.updatePreviewShape = function () {
  if (this.shape != null && this.pBounds != null) {
    this.shape.bounds = new mxRectangle(
      Math.round(this.pBounds.x + this.currentDx),
      Math.round(this.pBounds.y + this.currentDy),
      this.pBounds.width,
      this.pBounds.height
    );
    this.shape.redraw();
  }
};

/**
 * Function: updateLivePreview
 *
 * Updates the bounds of the preview shape.
 */
mxGraphHandler.prototype.updateLivePreview = function (dx, dy) {
  if (!this.suspended) {
    var states = [];

    if (this.allCells != null) {
      this.allCells.visit(
        mxUtils.bind(this, function (key, state) {
          var realState = this.graph.view.getState(state.cell);

          // Checks if cell was removed or replaced
          if (realState != state) {
            state.destroy();

            if (realState != null) {
              this.allCells.put(state.cell, realState);
            } else {
              this.allCells.remove(state.cell);
            }

            state = realState;
          }

          if (state != null) {
            // Saves current state
            var tempState = state.clone();
            states.push([state, tempState]);

            // Makes transparent for events to detect drop targets
            if (state.shape != null) {
              if (state.shape.originalPointerEvents == null) {
                state.shape.originalPointerEvents = state.shape.pointerEvents;
              }

              state.shape.pointerEvents = false;

              if (state.text != null) {
                if (state.text.originalPointerEvents == null) {
                  state.text.originalPointerEvents = state.text.pointerEvents;
                }

                state.text.pointerEvents = false;
              }
            }

            // Temporarily changes position
            if (this.graph.model.isVertex(state.cell)) {
              state.x += dx;
              state.y += dy;

              // Draws the live preview
              if (!this.cloning) {
                state.view.graph.cellRenderer.redraw(state, true);

                // Forces redraw of connected edges after all states
                // have been updated but avoids update of state
                state.view.invalidate(state.cell);
                state.invalid = false;

                // Hides folding icon
                if (state.control != null && state.control.node != null) {
                  state.control.node.style.visibility = 'hidden';
                }
              }
              // Clone live preview may use text bounds
              else if (state.text != null) {
                state.text.updateBoundingBox();

                // Fixes preview box for edge labels
                if (state.text.boundingBox != null) {
                  state.text.boundingBox.x += dx;
                  state.text.boundingBox.y += dy;
                }

                if (state.text.unrotatedBoundingBox != null) {
                  state.text.unrotatedBoundingBox.x += dx;
                  state.text.unrotatedBoundingBox.y += dy;
                }
              }
            }
          }
        })
      );
    }

    // Resets the handler if everything was removed
    if (states.length == 0) {
      this.reset();
    } else {
      // Redraws connected edges
      var s = this.graph.view.scale;

      for (var i = 0; i < states.length; i++) {
        var state = states[i][0];

        if (this.graph.model.isEdge(state.cell)) {
          var geometry = this.graph.getCellGeometry(state.cell);
          var points = [];

          if (geometry != null && geometry.points != null) {
            for (var j = 0; j < geometry.points.length; j++) {
              if (geometry.points[j] != null) {
                points.push(new mxPoint(geometry.points[j].x + dx / s, geometry.points[j].y + dy / s));
              }
            }
          }

          var source = state.visibleSourceState;
          var target = state.visibleTargetState;
          var pts = states[i][1].absolutePoints;

          if (source == null || !this.isCellMoving(source.cell)) {
            var pt0 = pts[0];
            state.setAbsoluteTerminalPoint(new mxPoint(pt0.x + dx, pt0.y + dy), true);
            source = null;
          } else {
            state.view.updateFixedTerminalPoint(
              state,
              source,
              true,
              this.graph.getConnectionConstraint(state, source, true)
            );
          }

          if (target == null || !this.isCellMoving(target.cell)) {
            var ptn = pts[pts.length - 1];
            state.setAbsoluteTerminalPoint(new mxPoint(ptn.x + dx, ptn.y + dy), false);
            target = null;
          } else {
            state.view.updateFixedTerminalPoint(
              state,
              target,
              false,
              this.graph.getConnectionConstraint(state, target, false)
            );
          }

          state.view.updatePoints(state, points, source, target);
          state.view.updateFloatingTerminalPoints(state, source, target);
          state.view.updateEdgeLabelOffset(state);
          state.invalid = false;

          // Draws the live preview but avoids update of state
          if (!this.cloning) {
            state.view.graph.cellRenderer.redraw(state, true);
          }
        }
      }

      this.graph.view.validate();
      this.redrawHandles(states);
      this.resetPreviewStates(states);
    }
  }
};

/**
 * Function: redrawHandles
 *
 * Redraws the preview shape for the given states array.
 */
mxGraphHandler.prototype.redrawHandles = function (states) {
  for (var i = 0; i < states.length; i++) {
    var handler = this.graph.selectionCellsHandler.getHandler(states[i][0].cell);

    if (handler != null) {
      handler.redraw(true);
    }
  }
};

/**
 * Function: resetPreviewStates
 *
 * Resets the given preview states array.
 */
mxGraphHandler.prototype.resetPreviewStates = function (states) {
  for (var i = 0; i < states.length; i++) {
    states[i][0].setState(states[i][1]);
  }
};

/**
 * Function: suspend
 *
 * Suspends the livew preview.
 */
mxGraphHandler.prototype.suspend = function () {
  if (!this.suspended) {
    if (this.livePreviewUsed) {
      this.updateLivePreview(0, 0);
    }

    if (this.shape != null) {
      this.shape.node.style.visibility = 'hidden';
    }

    if (this.guide != null) {
      this.guide.setVisible(false);
    }

    this.suspended = true;
  }
};

/**
 * Function: resume
 *
 * Suspends the livew preview.
 */
mxGraphHandler.prototype.resume = function () {
  if (this.suspended) {
    this.suspended = null;

    if (this.livePreviewUsed) {
      this.livePreviewActive = true;
    }

    if (this.shape != null) {
      this.shape.node.style.visibility = 'visible';
    }

    if (this.guide != null) {
      this.guide.setVisible(true);
    }
  }
};

/**
 * Function: resetLivePreview
 *
 * Resets the livew preview.
 */
mxGraphHandler.prototype.resetLivePreview = function () {
  if (this.allCells != null) {
    this.allCells.visit(
      mxUtils.bind(this, function (key, state) {
        // Restores event handling
        if (state.shape != null && state.shape.originalPointerEvents != null) {
          state.shape.pointerEvents = state.shape.originalPointerEvents;
          state.shape.originalPointerEvents = null;

          // Forces repaint even if not moved to update pointer events
          state.shape.bounds = null;

          if (state.text != null) {
            state.text.pointerEvents = state.text.originalPointerEvents;
            state.text.originalPointerEvents = null;
          }
        }

        // Shows folding icon
        if (state.control != null && state.control.node != null && state.control.node.style.visibility == 'hidden') {
          state.control.node.style.visibility = '';
        }

        // Fixes preview box for edge labels
        if (!this.cloning) {
          if (state.text != null) {
            state.text.updateBoundingBox();
          }
        }

        // Forces repaint of connected edges
        state.view.invalidate(state.cell);
      })
    );

    // Repaints all invalid states
    this.graph.view.validate();
  }
};

/**
 * Function: setHandlesVisibleForCells
 *
 * Sets wether the handles attached to the given cells are visible.
 *
 * Parameters:
 *
 * cells - Array of <mxCells>.
 * visible - Boolean that specifies if the handles should be visible.
 * force - Forces an update of the handler regardless of the last used value.
 */
mxGraphHandler.prototype.setHandlesVisibleForCells = function (cells, visible, force) {
  if (force || this.handlesVisible != visible) {
    this.handlesVisible = visible;

    for (var i = 0; i < cells.length; i++) {
      var handler = this.graph.selectionCellsHandler.getHandler(cells[i]);

      if (handler != null) {
        handler.setHandlesVisible(visible);

        if (visible) {
          handler.redraw();
        }
      }
    }
  }
};

/**
 * Function: setHighlightColor
 *
 * Sets the color of the rectangle used to highlight drop targets.
 *
 * Parameters:
 *
 * color - String that represents the new highlight color.
 */
mxGraphHandler.prototype.setHighlightColor = function (color) {
  if (this.highlight != null) {
    this.highlight.setHighlightColor(color);
  }
};

/**
 * Function: mouseUp
 *
 * Handles the event by applying the changes to the selection cells.
 */
mxGraphHandler.prototype.mouseUp = function (sender, me) {
  if (!me.isConsumed()) {
    if (this.livePreviewUsed) {
      this.resetLivePreview();
    }

    if (
      this.cell != null &&
      this.first != null &&
      (this.shape != null || this.livePreviewUsed) &&
      this.currentDx != null &&
      this.currentDy != null
    ) {
      var graph = this.graph;
      var cell = me.getCell();

      if (
        this.connectOnDrop &&
        this.target == null &&
        cell != null &&
        graph.getModel().isVertex(cell) &&
        graph.isCellConnectable(cell) &&
        graph.isEdgeValid(null, this.cell, cell)
      ) {
        graph.connectionHandler.connect(this.cell, cell, me.getEvent());
      } else {
        var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
        var scale = graph.getView().scale;
        var dx = this.roundLength(this.currentDx / scale);
        var dy = this.roundLength(this.currentDy / scale);
        var target = this.target;

        if (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent())) {
          graph.splitEdge(target, this.cells, null, dx, dy, me.getGraphX(), me.getGraphY());
        } else {
          this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent());
        }
      }
    } else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null) {
      this.selectDelayed(me);
    }
  }

  // Consumes the event if a cell was initially clicked
  if (this.cellWasClicked) {
    this.consumeMouseEvent(mxEvent.MOUSE_UP, me);
  }

  this.reset();
};

/**
 * Function: reset
 *
 * Resets the state of this handler.
 */
mxGraphHandler.prototype.reset = function () {
  if (this.livePreviewUsed) {
    this.resetLivePreview();
    this.setHandlesVisibleForCells(this.graph.selectionCellsHandler.getHandledSelectionCells(), true);
  }

  this.destroyShapes();
  this.removeHint();

  this.delayedSelection = false;
  this.livePreviewActive = null;
  this.livePreviewUsed = null;
  this.cellWasClicked = false;
  this.suspended = null;
  this.currentDx = null;
  this.currentDy = null;
  this.cellCount = null;
  this.cloning = false;
  this.allCells = null;
  this.pBounds = null;
  this.guides = null;
  this.target = null;
  this.first = null;
  this.cells = null;
  this.cell = null;
};

/**
 * Function: shouldRemoveCellsFromParent
 *
 * Returns true if the given cells should be removed from the parent for the specified
 * mousereleased event.
 */
mxGraphHandler.prototype.shouldRemoveCellsFromParent = function (parent, cells, evt) {
  if (this.graph.getModel().isVertex(parent)) {
    var pState = this.graph.getView().getState(parent);

    if (pState != null) {
      var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
      var alpha = mxUtils.toRadians(mxUtils.getValue(pState.style, mxConstants.STYLE_ROTATION) || 0);

      if (alpha != 0) {
        var cos = Math.cos(-alpha);
        var sin = Math.sin(-alpha);
        var cx = new mxPoint(pState.getCenterX(), pState.getCenterY());
        pt = mxUtils.getRotatedPoint(pt, cos, sin, cx);
      }

      return !mxUtils.contains(pState, pt.x, pt.y);
    }
  }

  return false;
};

/**
 * Function: moveCells
 *
 * Moves the given cells by the specified amount.
 */
mxGraphHandler.prototype.moveCells = function (cells, dx, dy, clone, target, evt) {
  if (clone) {
    cells = this.graph.getCloneableCells(cells);
  }

  // Removes cells from parent
  var parent = this.graph.getModel().getParent(this.cell);

  if (target == null && this.isRemoveCellsFromParent() && this.shouldRemoveCellsFromParent(parent, cells, evt)) {
    target = this.graph.getDefaultParent();
  }

  // Cloning into locked cells is not allowed
  clone = clone && !this.graph.isCellLocked(target || this.graph.getDefaultParent());

  this.graph.getModel().beginUpdate();
  try {
    var parents = [];

    // Removes parent if all child cells are removed
    if (!clone && target != null && this.removeEmptyParents) {
      // Collects all non-selected parents
      var dict = new mxDictionary();

      for (var i = 0; i < cells.length; i++) {
        dict.put(cells[i], true);
      }

      // LATER: Recurse up the cell hierarchy
      for (var i = 0; i < cells.length; i++) {
        var par = this.graph.model.getParent(cells[i]);

        if (par != null && !dict.get(par)) {
          dict.put(par, true);
          parents.push(par);
        }
      }
    }

    // Passes all selected cells in order to correctly clone or move into
    // the target cell. The method checks for each cell if its movable.
    cells = this.graph.moveCells(cells, dx, dy, clone, target, evt);

    // Removes parent if all child cells are removed
    var temp = [];

    for (var i = 0; i < parents.length; i++) {
      if (this.shouldRemoveParent(parents[i])) {
        temp.push(parents[i]);
      }
    }

    this.graph.removeCells(temp, false);
  } finally {
    this.graph.getModel().endUpdate();
  }

  // Selects the new cells if cells have been cloned
  if (clone) {
    this.graph.setSelectionCells(cells);
  }

  if (this.isSelectEnabled() && this.scrollOnMove) {
    this.graph.scrollCellToVisible(cells[0]);
  }
};

/**
 * Function: shouldRemoveParent
 *
 * Returns true if the given parent should be removed after removal of child cells.
 */
mxGraphHandler.prototype.shouldRemoveParent = function (parent) {
  var state = this.graph.view.getState(parent);

  return (
    state != null &&
    (this.graph.model.isEdge(state.cell) || this.graph.model.isVertex(state.cell)) &&
    this.graph.isCellDeletable(state.cell) &&
    this.graph.model.getChildCount(state.cell) == 0 &&
    this.graph.isTransparentState(state)
  );
};

/**
 * Function: destroyShapes
 *
 * Destroy the preview and highlight shapes.
 */
mxGraphHandler.prototype.destroyShapes = function () {
  // Destroys the preview dashed rectangle
  if (this.shape != null) {
    this.shape.destroy();
    this.shape = null;
  }

  if (this.guide != null) {
    this.guide.destroy();
    this.guide = null;
  }

  // Destroys the drop target highlight
  if (this.highlight != null) {
    this.highlight.destroy();
    this.highlight = null;
  }
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes.
 */
mxGraphHandler.prototype.destroy = function () {
  this.graph.removeMouseListener(this);
  this.graph.removeListener(this.panHandler);

  if (this.escapeHandler != null) {
    this.graph.removeListener(this.escapeHandler);
    this.escapeHandler = null;
  }

  if (this.refreshHandler != null) {
    this.graph.getModel().removeListener(this.refreshHandler);
    this.graph.removeListener(this.refreshHandler);
    this.refreshHandler = null;
  }

  mxEvent.removeListener(document, 'keydown', this.keyHandler);
  mxEvent.removeListener(document, 'keyup', this.keyHandler);

  this.destroyShapes();
  this.removeHint();
};

/**
 * handler/mxPanningHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPanningHandler
 *
 * Event handler that pans and creates popupmenus. To use the left
 * mousebutton for panning without interfering with cell moving and
 * resizing, use <isUseLeftButton> and <isIgnoreCell>. For grid size
 * steps while panning, use <useGrid>. This handler is built-into
 * <mxGraph.panningHandler> and enabled using <mxGraph.setPanning>.
 *
 * Constructor: mxPanningHandler
 *
 * Constructs an event handler that creates a <mxPopupMenu>
 * and pans the graph.
 *
 * Event: mxEvent.PAN_START
 *
 * Fires when the panning handler changes its <active> state to true. The
 * <code>event</code> property contains the corresponding <mxMouseEvent>.
 *
 * Event: mxEvent.PAN
 *
 * Fires while handle is processing events. The <code>event</code> property contains
 * the corresponding <mxMouseEvent>.
 *
 * Event: mxEvent.PAN_END
 *
 * Fires when the panning handler changes its <active> state to false. The
 * <code>event</code> property contains the corresponding <mxMouseEvent>.
 */
function mxPanningHandler(graph) {
  if (graph != null) {
    this.graph = graph;
    this.graph.addMouseListener(this);

    // Handles force panning event
    this.forcePanningHandler = mxUtils.bind(this, function (sender, evt) {
      var evtName = evt.getProperty('eventName');
      var me = evt.getProperty('event');

      if (evtName == mxEvent.MOUSE_DOWN && this.isForcePanningEvent(me)) {
        this.start(me);
        this.active = true;
        this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
        me.consume();
      }
    });

    this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forcePanningHandler);

    // Handles pinch gestures
    this.gestureHandler = mxUtils.bind(this, function (sender, eo) {
      if (this.isPinchEnabled()) {
        var evt = eo.getProperty('event');

        if (!mxEvent.isConsumed(evt) && evt.type == 'gesturestart') {
          this.initialScale = this.graph.view.scale;

          // Forces start of panning when pinch gesture starts
          if (!this.active && this.mouseDownEvent != null) {
            this.start(this.mouseDownEvent);
            this.mouseDownEvent = null;
          }
        } else if (evt.type == 'gestureend' && this.initialScale != null) {
          this.initialScale = null;
        }

        if (this.initialScale != null) {
          this.zoomGraph(evt);
        }
      }
    });

    this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);

    this.mouseUpListener = mxUtils.bind(this, function () {
      if (this.active) {
        this.reset();
      }
    });

    // Stops scrolling on every mouseup anywhere in the document
    mxEvent.addListener(document, 'mouseup', this.mouseUpListener);
  }
}

/**
 * Extends mxEventSource.
 */
mxPanningHandler.prototype = new mxEventSource();
mxPanningHandler.prototype.constructor = mxPanningHandler;

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxPanningHandler.prototype.graph = null;

/**
 * Variable: useLeftButtonForPanning
 *
 * Specifies if panning should be active for the left mouse button.
 * Setting this to true may conflict with <mxRubberband>. Default is false.
 */
mxPanningHandler.prototype.useLeftButtonForPanning = false;

/**
 * Variable: usePopupTrigger
 *
 * Specifies if <mxEvent.isPopupTrigger> should also be used for panning.
 */
mxPanningHandler.prototype.usePopupTrigger = true;

/**
 * Variable: ignoreCell
 *
 * Specifies if panning should be active even if there is a cell under the
 * mousepointer. Default is false.
 */
mxPanningHandler.prototype.ignoreCell = false;

/**
 * Variable: previewEnabled
 *
 * Specifies if the panning should be previewed. Default is true.
 */
mxPanningHandler.prototype.previewEnabled = true;

/**
 * Variable: useGrid
 *
 * Specifies if the panning steps should be aligned to the grid size.
 * Default is false.
 */
mxPanningHandler.prototype.useGrid = false;

/**
 * Variable: panningEnabled
 *
 * Specifies if panning should be enabled. Default is true.
 */
mxPanningHandler.prototype.panningEnabled = true;

/**
 * Variable: pinchEnabled
 *
 * Specifies if pinch gestures should be handled as zoom. Default is true.
 */
mxPanningHandler.prototype.pinchEnabled = true;

/**
 * Variable: maxScale
 *
 * Specifies the maximum scale. Default is 8.
 */
mxPanningHandler.prototype.maxScale = 8;

/**
 * Variable: minScale
 *
 * Specifies the minimum scale. Default is 0.01.
 */
mxPanningHandler.prototype.minScale = 0.01;

/**
 * Variable: dx
 *
 * Holds the current horizontal offset.
 */
mxPanningHandler.prototype.dx = null;

/**
 * Variable: dy
 *
 * Holds the current vertical offset.
 */
mxPanningHandler.prototype.dy = null;

/**
 * Variable: startX
 *
 * Holds the x-coordinate of the start point.
 */
mxPanningHandler.prototype.startX = 0;

/**
 * Variable: startY
 *
 * Holds the y-coordinate of the start point.
 */
mxPanningHandler.prototype.startY = 0;

/**
 * Function: isActive
 *
 * Returns true if the handler is currently active.
 */
mxPanningHandler.prototype.isActive = function () {
  return this.active || this.initialScale != null;
};

/**
 * Function: isPanningEnabled
 *
 * Returns <panningEnabled>.
 */
mxPanningHandler.prototype.isPanningEnabled = function () {
  return this.panningEnabled;
};

/**
 * Function: setPanningEnabled
 *
 * Sets <panningEnabled>.
 */
mxPanningHandler.prototype.setPanningEnabled = function (value) {
  this.panningEnabled = value;
};

/**
 * Function: isPinchEnabled
 *
 * Returns <pinchEnabled>.
 */
mxPanningHandler.prototype.isPinchEnabled = function () {
  return this.pinchEnabled;
};

/**
 * Function: setPinchEnabled
 *
 * Sets <pinchEnabled>.
 */
mxPanningHandler.prototype.setPinchEnabled = function (value) {
  this.pinchEnabled = value;
};

/**
 * Function: isPanningTrigger
 *
 * Returns true if the given event is a panning trigger for the optional
 * given cell. This returns true if control-shift is pressed or if
 * <usePopupTrigger> is true and the event is a popup trigger.
 */
mxPanningHandler.prototype.isPanningTrigger = function (me) {
  var evt = me.getEvent();

  return (
    (this.useLeftButtonForPanning && me.getState() == null && mxEvent.isLeftMouseButton(evt)) ||
    (mxEvent.isControlDown(evt) && mxEvent.isShiftDown(evt)) ||
    (this.usePopupTrigger && mxEvent.isPopupTrigger(evt))
  );
};

/**
 * Function: isForcePanningEvent
 *
 * Returns true if the given <mxMouseEvent> should start panning. This
 * implementation always returns true if <ignoreCell> is true or for
 * multi touch events.
 */
mxPanningHandler.prototype.isForcePanningEvent = function (me) {
  return this.ignoreCell || mxEvent.isMultiTouchEvent(me.getEvent());
};

/**
 * Function: mouseDown
 *
 * Handles the event by initiating the panning. By consuming the event all
 * subsequent events of the gesture are redirected to this handler.
 */
mxPanningHandler.prototype.mouseDown = function (sender, me) {
  this.mouseDownEvent = me;

  if (!me.isConsumed() && this.isPanningEnabled() && !this.active && this.isPanningTrigger(me)) {
    this.start(me);
    this.consumePanningTrigger(me);
  }
};

/**
 * Function: start
 *
 * Starts panning at the given event.
 */
mxPanningHandler.prototype.start = function (me) {
  this.dx0 = -this.graph.container.scrollLeft;
  this.dy0 = -this.graph.container.scrollTop;

  // Stores the location of the trigger event
  this.startX = me.getX();
  this.startY = me.getY();
  this.dx = null;
  this.dy = null;

  this.panningTrigger = true;
};

/**
 * Function: consumePanningTrigger
 *
 * Consumes the given <mxMouseEvent> if it was a panning trigger in
 * <mouseDown>. The default is to invoke <mxMouseEvent.consume>. Note that this
 * will block any further event processing. If you haven't disabled built-in
 * context menus and require immediate selection of the cell on mouseDown in
 * Safari and/or on the Mac, then use the following code:
 *
 * (code)
 * mxPanningHandler.prototype.consumePanningTrigger = function(me)
 * {
 *   if (me.evt.preventDefault)
 *   {
 *     me.evt.preventDefault();
 *   }
 *
 *   // Stops event processing in IE
 *   me.evt.returnValue = false;
 *
 *   // Sets local consumed state
 *   if (!mxClient.IS_SF && !mxClient.IS_MAC)
 *   {
 *     me.consumed = true;
 *   }
 * };
 * (end)
 */
mxPanningHandler.prototype.consumePanningTrigger = function (me) {
  me.consume();
};

/**
 * Function: mouseMove
 *
 * Handles the event by updating the panning on the graph.
 */
mxPanningHandler.prototype.mouseMove = function (sender, me) {
  this.dx = me.getX() - this.startX;
  this.dy = me.getY() - this.startY;

  if (this.active) {
    if (this.previewEnabled) {
      // Applies the grid to the panning steps
      if (this.useGrid) {
        this.dx = this.graph.snap(this.dx);
        this.dy = this.graph.snap(this.dy);
      }

      this.graph.panGraph(this.dx + this.dx0, this.dy + this.dy0);
    }

    this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me));
  } else if (this.panningTrigger) {
    var tmp = this.active;

    // Panning is activated only if the mouse is moved
    // beyond the graph tolerance
    this.active = Math.abs(this.dx) > this.graph.tolerance || Math.abs(this.dy) > this.graph.tolerance;

    if (!tmp && this.active) {
      this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
    }
  }

  if (this.active || this.panningTrigger) {
    me.consume();
  }
};

/**
 * Function: mouseUp
 *
 * Handles the event by setting the translation on the view or showing the
 * popupmenu.
 */
mxPanningHandler.prototype.mouseUp = function (sender, me) {
  if (this.active) {
    if (this.dx != null && this.dy != null) {
      // Ignores if scrollbars have been used for panning
      if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container)) {
        var scale = this.graph.getView().scale;
        var t = this.graph.getView().translate;
        this.graph.panGraph(0, 0);
        this.panGraph(t.x + this.dx / scale, t.y + this.dy / scale);
      }

      me.consume();
    }

    this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me));
  }

  this.reset();
};

/**
 * Function: zoomGraph
 *
 * Zooms the graph to the given value and consumed the event if needed.
 */
mxPanningHandler.prototype.zoomGraph = function (evt) {
  var value = Math.round(this.initialScale * evt.scale * 100) / 100;

  if (this.minScale != null) {
    value = Math.max(this.minScale, value);
  }

  if (this.maxScale != null) {
    value = Math.min(this.maxScale, value);
  }

  if (this.graph.view.scale != value) {
    this.graph.zoomTo(value);
    mxEvent.consume(evt);
  }
};

/**
 * Function: reset
 *
 * Resets the state of this handler.
 */
mxPanningHandler.prototype.reset = function () {
  this.panningTrigger = false;
  this.mouseDownEvent = null;
  this.active = false;
  this.dx = null;
  this.dy = null;
};

/**
 * Function: panGraph
 *
 * Pans <graph> by the given amount.
 */
mxPanningHandler.prototype.panGraph = function (dx, dy) {
  this.graph.getView().setTranslate(dx, dy);
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes.
 */
mxPanningHandler.prototype.destroy = function () {
  this.graph.removeMouseListener(this);
  this.graph.removeListener(this.forcePanningHandler);
  this.graph.removeListener(this.gestureHandler);
  mxEvent.removeListener(document, 'mouseup', this.mouseUpListener);
};

/**
 * handler/mxPopupMenuHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPopupMenuHandler
 *
 * Event handler that creates popupmenus.
 *
 * Constructor: mxPopupMenuHandler
 *
 * Constructs an event handler that creates a <mxPopupMenu>.
 */
function mxPopupMenuHandler(graph, factoryMethod) {
  if (graph != null) {
    this.graph = graph;
    this.factoryMethod = factoryMethod;
    this.graph.addMouseListener(this);

    // Does not show menu if any touch gestures take place after the trigger
    this.gestureHandler = mxUtils.bind(this, function (sender, eo) {
      this.inTolerance = false;
    });

    this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);

    this.init();
  }
}

/**
 * Extends mxPopupMenu.
 */
mxPopupMenuHandler.prototype = new mxPopupMenu();
mxPopupMenuHandler.prototype.constructor = mxPopupMenuHandler;

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxPopupMenuHandler.prototype.graph = null;

/**
 * Variable: selectOnPopup
 *
 * Specifies if cells should be selected if a popupmenu is displayed for
 * them. Default is true.
 */
mxPopupMenuHandler.prototype.selectOnPopup = true;

/**
 * Variable: clearSelectionOnBackground
 *
 * Specifies if cells should be deselected if a popupmenu is displayed for
 * the diagram background. Default is true.
 */
mxPopupMenuHandler.prototype.clearSelectionOnBackground = true;

/**
 * Variable: triggerX
 *
 * X-coordinate of the mouse down event.
 */
mxPopupMenuHandler.prototype.triggerX = null;

/**
 * Variable: triggerY
 *
 * Y-coordinate of the mouse down event.
 */
mxPopupMenuHandler.prototype.triggerY = null;

/**
 * Variable: screenX
 *
 * Screen X-coordinate of the mouse down event.
 */
mxPopupMenuHandler.prototype.screenX = null;

/**
 * Variable: screenY
 *
 * Screen Y-coordinate of the mouse down event.
 */
mxPopupMenuHandler.prototype.screenY = null;

/**
 * Function: init
 *
 * Initializes the shapes required for this vertex handler.
 */
mxPopupMenuHandler.prototype.init = function () {
  // Supercall
  mxPopupMenu.prototype.init.apply(this);

  // Hides the tooltip if the mouse is over
  // the context menu
  mxEvent.addGestureListeners(
    this.div,
    mxUtils.bind(this, function (evt) {
      this.graph.tooltipHandler.hide();
    })
  );
};

/**
 * Function: isSelectOnPopup
 *
 * Hook for returning if a cell should be selected for a given <mxMouseEvent>.
 * This implementation returns <selectOnPopup>.
 */
mxPopupMenuHandler.prototype.isSelectOnPopup = function (me) {
  return this.selectOnPopup;
};

/**
 * Function: mouseDown
 *
 * Handles the event by initiating the panning. By consuming the event all
 * subsequent events of the gesture are redirected to this handler.
 */
mxPopupMenuHandler.prototype.mouseDown = function (sender, me) {
  if (this.isEnabled() && !mxEvent.isMultiTouchEvent(me.getEvent())) {
    // Hides the popupmenu if is is being displayed
    this.hideMenu();
    this.triggerX = me.getGraphX();
    this.triggerY = me.getGraphY();
    this.screenX = mxEvent.getMainEvent(me.getEvent()).screenX;
    this.screenY = mxEvent.getMainEvent(me.getEvent()).screenY;
    this.popupTrigger = this.isPopupTrigger(me);
    this.inTolerance = true;
  }
};

/**
 * Function: mouseMove
 *
 * Handles the event by updating the panning on the graph.
 */
mxPopupMenuHandler.prototype.mouseMove = function (sender, me) {
  // Popup trigger may change on mouseUp so ignore it
  if (this.inTolerance && this.screenX != null && this.screenY != null) {
    if (
      Math.abs(mxEvent.getMainEvent(me.getEvent()).screenX - this.screenX) > this.graph.tolerance ||
      Math.abs(mxEvent.getMainEvent(me.getEvent()).screenY - this.screenY) > this.graph.tolerance
    ) {
      this.inTolerance = false;
    }
  }
};

/**
 * Function: mouseUp
 *
 * Handles the event by setting the translation on the view or showing the
 * popupmenu.
 */
mxPopupMenuHandler.prototype.mouseUp = function (sender, me) {
  if (this.popupTrigger && this.inTolerance && this.triggerX != null && this.triggerY != null) {
    var cell = this.getCellForPopupEvent(me);

    // Selects the cell for which the context menu is being displayed
    if (this.graph.isEnabled() && this.isSelectOnPopup(me) && cell != null && !this.graph.isCellSelected(cell)) {
      this.graph.setSelectionCell(cell);
    } else if (this.clearSelectionOnBackground && cell == null) {
      this.graph.clearSelection();
    }

    // Hides the tooltip if there is one
    this.graph.tooltipHandler.hide();

    // Menu is shifted by 1 pixel so that the mouse up event
    // is routed via the underlying shape instead of the DIV
    var origin = mxUtils.getScrollOrigin();
    this.popup(me.getX() + origin.x + 1, me.getY() + origin.y + 1, cell, me.getEvent());
    me.consume();
  }

  this.popupTrigger = false;
  this.inTolerance = false;
};

/**
 * Function: getCellForPopupEvent
 *
 * Hook to return the cell for the mouse up popup trigger handling.
 */
mxPopupMenuHandler.prototype.getCellForPopupEvent = function (me) {
  return me.getCell();
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes.
 */
mxPopupMenuHandler.prototype.destroy = function () {
  this.graph.removeMouseListener(this);
  this.graph.removeListener(this.gestureHandler);

  // Supercall
  mxPopupMenu.prototype.destroy.apply(this);
};

/**
 * handler/mxCellMarker.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCellMarker
 *
 * A helper class to process mouse locations and highlight cells.
 *
 * Helper class to highlight cells. To add a cell marker to an existing graph
 * for highlighting all cells, the following code is used:
 *
 * (code)
 * var marker = new mxCellMarker(graph);
 * graph.addMouseListener({
 *   mouseDown: function() {},
 *   mouseMove: function(sender, me)
 *   {
 *     marker.process(me);
 *   },
 *   mouseUp: function() {}
 * });
 * (end)
 *
 * Event: mxEvent.MARK
 *
 * Fires after a cell has been marked or unmarked. The <code>state</code>
 * property contains the marked <mxCellState> or null if no state is marked.
 *
 * Constructor: mxCellMarker
 *
 * Constructs a new cell marker.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 * validColor - Optional marker color for valid states. Default is
 * <mxConstants.DEFAULT_VALID_COLOR>.
 * invalidColor - Optional marker color for invalid states. Default is
 * <mxConstants.DEFAULT_INVALID_COLOR>.
 * hotspot - Portion of the width and hight where a state intersects a
 * given coordinate pair. A value of 0 means always highlight. Default is
 * <mxConstants.DEFAULT_HOTSPOT>.
 */
function mxCellMarker(graph, validColor, invalidColor, hotspot) {
  mxEventSource.call(this);

  if (graph != null) {
    this.graph = graph;
    this.validColor = validColor != null ? validColor : mxConstants.DEFAULT_VALID_COLOR;
    this.invalidColor = invalidColor != null ? invalidColor : mxConstants.DEFAULT_INVALID_COLOR;
    this.hotspot = hotspot != null ? hotspot : mxConstants.DEFAULT_HOTSPOT;

    this.highlight = new mxCellHighlight(graph);
  }
}

/**
 * Extends mxEventSource.
 */
mxUtils.extend(mxCellMarker, mxEventSource);

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxCellMarker.prototype.graph = null;

/**
 * Variable: enabled
 *
 * Specifies if the marker is enabled. Default is true.
 */
mxCellMarker.prototype.enabled = true;

/**
 * Variable: hotspot
 *
 * Specifies the portion of the width and height that should trigger
 * a highlight. The area around the center of the cell to be marked is used
 * as the hotspot. Possible values are between 0 and 1. Default is
 * mxConstants.DEFAULT_HOTSPOT.
 */
mxCellMarker.prototype.hotspot = mxConstants.DEFAULT_HOTSPOT;

/**
 * Variable: hotspotEnabled
 *
 * Specifies if the hotspot is enabled. Default is false.
 */
mxCellMarker.prototype.hotspotEnabled = false;

/**
 * Variable: validColor
 *
 * Holds the valid marker color.
 */
mxCellMarker.prototype.validColor = null;

/**
 * Variable: invalidColor
 *
 * Holds the invalid marker color.
 */
mxCellMarker.prototype.invalidColor = null;

/**
 * Variable: currentColor
 *
 * Holds the current marker color.
 */
mxCellMarker.prototype.currentColor = null;

/**
 * Variable: validState
 *
 * Holds the marked <mxCellState> if it is valid.
 */
mxCellMarker.prototype.validState = null;

/**
 * Variable: markedState
 *
 * Holds the marked <mxCellState>.
 */
mxCellMarker.prototype.markedState = null;

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 *
 * Parameters:
 *
 * enabled - Boolean that specifies the new enabled state.
 */
mxCellMarker.prototype.setEnabled = function (enabled) {
  this.enabled = enabled;
};

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxCellMarker.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setHotspot
 *
 * Sets the <hotspot>.
 */
mxCellMarker.prototype.setHotspot = function (hotspot) {
  this.hotspot = hotspot;
};

/**
 * Function: getHotspot
 *
 * Returns the <hotspot>.
 */
mxCellMarker.prototype.getHotspot = function () {
  return this.hotspot;
};

/**
 * Function: setHotspotEnabled
 *
 * Specifies whether the hotspot should be used in <intersects>.
 */
mxCellMarker.prototype.setHotspotEnabled = function (enabled) {
  this.hotspotEnabled = enabled;
};

/**
 * Function: isHotspotEnabled
 *
 * Returns true if hotspot is used in <intersects>.
 */
mxCellMarker.prototype.isHotspotEnabled = function () {
  return this.hotspotEnabled;
};

/**
 * Function: hasValidState
 *
 * Returns true if <validState> is not null.
 */
mxCellMarker.prototype.hasValidState = function () {
  return this.validState != null;
};

/**
 * Function: getValidState
 *
 * Returns the <validState>.
 */
mxCellMarker.prototype.getValidState = function () {
  return this.validState;
};

/**
 * Function: getMarkedState
 *
 * Returns the <markedState>.
 */
mxCellMarker.prototype.getMarkedState = function () {
  return this.markedState;
};

/**
 * Function: reset
 *
 * Resets the state of the cell marker.
 */
mxCellMarker.prototype.reset = function () {
  this.validState = null;

  if (this.markedState != null) {
    this.markedState = null;
    this.unmark();
  }
};

/**
 * Function: process
 *
 * Processes the given event and cell and marks the state returned by
 * <getState> with the color returned by <getMarkerColor>. If the
 * markerColor is not null, then the state is stored in <markedState>. If
 * <isValidState> returns true, then the state is stored in <validState>
 * regardless of the marker color. The state is returned regardless of the
 * marker color and valid state.
 */
mxCellMarker.prototype.process = function (me) {
  var state = null;

  if (this.isEnabled()) {
    state = this.getState(me);
    this.setCurrentState(state, me);
  }

  return state;
};

/**
 * Function: setCurrentState
 *
 * Sets and marks the current valid state.
 */
mxCellMarker.prototype.setCurrentState = function (state, me, color) {
  var isValid = state != null ? this.isValidState(state) : false;
  color = color != null ? color : this.getMarkerColor(me.getEvent(), state, isValid);

  if (isValid) {
    this.validState = state;
  } else {
    this.validState = null;
  }

  if (state != this.markedState || color != this.currentColor) {
    this.currentColor = color;

    if (state != null && this.currentColor != null) {
      this.markedState = state;
      this.mark();
    } else if (this.markedState != null) {
      this.markedState = null;
      this.unmark();
    }
  }
};

/**
 * Function: markCell
 *
 * Marks the given cell using the given color, or <validColor> if no color is specified.
 */
mxCellMarker.prototype.markCell = function (cell, color) {
  var state = this.graph.getView().getState(cell);

  if (state != null) {
    this.currentColor = color != null ? color : this.validColor;
    this.markedState = state;
    this.mark();
  }
};

/**
 * Function: mark
 *
 * Marks the <markedState> and fires a <mark> event.
 */
mxCellMarker.prototype.mark = function () {
  this.highlight.setHighlightColor(this.currentColor);
  this.highlight.highlight(this.markedState);
  this.fireEvent(new mxEventObject(mxEvent.MARK, 'state', this.markedState));
};

/**
 * Function: unmark
 *
 * Hides the marker and fires a <mark> event.
 */
mxCellMarker.prototype.unmark = function () {
  this.mark();
};

/**
 * Function: isValidState
 *
 * Returns true if the given <mxCellState> is a valid state. If this
 * returns true, then the state is stored in <validState>. The return value
 * of this method is used as the argument for <getMarkerColor>.
 */
mxCellMarker.prototype.isValidState = function (state) {
  return true;
};

/**
 * Function: getMarkerColor
 *
 * Returns the valid- or invalidColor depending on the value of isValid.
 * The given <mxCellState> is ignored by this implementation.
 */
mxCellMarker.prototype.getMarkerColor = function (evt, state, isValid) {
  return isValid ? this.validColor : this.invalidColor;
};

/**
 * Function: getState
 *
 * Uses <getCell>, <getStateToMark> and <intersects> to return the
 * <mxCellState> for the given <mxMouseEvent>.
 */
mxCellMarker.prototype.getState = function (me) {
  var view = this.graph.getView();
  var cell = this.getCell(me);
  var state = this.getStateToMark(view.getState(cell));

  return state != null && this.intersects(state, me) ? state : null;
};

/**
 * Function: getCell
 *
 * Returns the <mxCell> for the given event and cell. This returns the
 * given cell.
 */
mxCellMarker.prototype.getCell = function (me) {
  return me.getCell();
};

/**
 * Function: getStateToMark
 *
 * Returns the <mxCellState> to be marked for the given <mxCellState> under
 * the mouse. This returns the given state.
 */
mxCellMarker.prototype.getStateToMark = function (state) {
  return state;
};

/**
 * Function: intersects
 *
 * Returns true if the given coordinate pair intersects the given state.
 * This returns true if the <hotspot> is 0 or the coordinates are inside
 * the hotspot for the given cell state.
 */
mxCellMarker.prototype.intersects = function (state, me) {
  if (this.hotspotEnabled) {
    return mxUtils.intersectsHotspot(
      state,
      me.getGraphX(),
      me.getGraphY(),
      this.hotspot,
      mxConstants.MIN_HOTSPOT_SIZE,
      mxConstants.MAX_HOTSPOT_SIZE
    );
  }

  return true;
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes.
 */
mxCellMarker.prototype.destroy = function () {
  this.graph.getView().removeListener(this.resetHandler);
  this.graph.getModel().removeListener(this.resetHandler);
  this.highlight.destroy();
};

/**
 * handler/mxSelectionCellsHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxSelectionCellsHandler
 *
 * An event handler that manages cell handlers and invokes their mouse event
 * processing functions.
 *
 * Group: Events
 *
 * Event: mxEvent.ADD
 *
 * Fires if a cell has been added to the selection. The <code>state</code>
 * property contains the <mxCellState> that has been added.
 *
 * Event: mxEvent.REMOVE
 *
 * Fires if a cell has been remove from the selection. The <code>state</code>
 * property contains the <mxCellState> that has been removed.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 */
function mxSelectionCellsHandler(graph) {
  mxEventSource.call(this);

  this.graph = graph;
  this.handlers = new mxDictionary();
  this.graph.addMouseListener(this);

  this.refreshHandler = mxUtils.bind(this, function (sender, evt) {
    if (this.isEnabled()) {
      this.refresh();
    }
  });

  this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler);
  this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);
  this.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler);
  this.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler);
  this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler);
  this.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler);
  this.graph.getView().addListener(mxEvent.UP, this.refreshHandler);
}

/**
 * Extends mxEventSource.
 */
mxUtils.extend(mxSelectionCellsHandler, mxEventSource);

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxSelectionCellsHandler.prototype.graph = null;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxSelectionCellsHandler.prototype.enabled = true;

/**
 * Variable: refreshHandler
 *
 * Keeps a reference to an event listener for later removal.
 */
mxSelectionCellsHandler.prototype.refreshHandler = null;

/**
 * Variable: maxHandlers
 *
 * Defines the maximum number of handlers to paint individually. Default is 100.
 */
mxSelectionCellsHandler.prototype.maxHandlers = 100;

/**
 * Variable: handlers
 *
 * <mxDictionary> that maps from cells to handlers.
 */
mxSelectionCellsHandler.prototype.handlers = null;

/**
 * Function: isEnabled
 *
 * Returns <enabled>.
 */
mxSelectionCellsHandler.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Sets <enabled>.
 */
mxSelectionCellsHandler.prototype.setEnabled = function (value) {
  this.enabled = value;
};

/**
 * Function: getHandler
 *
 * Returns the handler for the given cell.
 */
mxSelectionCellsHandler.prototype.getHandler = function (cell) {
  return this.handlers.get(cell);
};

/**
 * Function: isHandled
 *
 * Returns true if the given cell has a handler.
 */
mxSelectionCellsHandler.prototype.isHandled = function (cell) {
  return this.getHandler(cell) != null;
};

/**
 * Function: reset
 *
 * Resets all handlers.
 */
mxSelectionCellsHandler.prototype.reset = function () {
  this.handlers.visit(function (key, handler) {
    handler.reset.apply(handler);
  });
};

/**
 * Function: getHandledSelectionCells
 *
 * Reloads or updates all handlers.
 */
mxSelectionCellsHandler.prototype.getHandledSelectionCells = function () {
  return this.graph.getSelectionCells();
};

/**
 * Function: refresh
 *
 * Reloads or updates all handlers.
 */
mxSelectionCellsHandler.prototype.refresh = function () {
  // Removes all existing handlers
  var oldHandlers = this.handlers;
  this.handlers = new mxDictionary();

  // Creates handles for all selection cells
  var tmp = mxUtils.sortCells(this.getHandledSelectionCells(), false);

  // Destroys or updates old handlers
  for (var i = 0; i < tmp.length; i++) {
    var state = this.graph.view.getState(tmp[i]);

    if (state != null) {
      var handler = oldHandlers.remove(tmp[i]);

      if (handler != null) {
        if (handler.state != state) {
          handler.destroy();
          handler = null;
        } else if (!this.isHandlerActive(handler)) {
          if (handler.refresh != null) {
            handler.refresh();
          }

          handler.redraw();
        }
      }

      if (handler != null) {
        this.handlers.put(tmp[i], handler);
      }
    }
  }

  // Destroys unused handlers
  oldHandlers.visit(
    mxUtils.bind(this, function (key, handler) {
      this.fireEvent(new mxEventObject(mxEvent.REMOVE, 'state', handler.state));
      handler.destroy();
    })
  );

  // Creates new handlers and updates parent highlight on existing handlers
  for (var i = 0; i < tmp.length; i++) {
    var state = this.graph.view.getState(tmp[i]);

    if (state != null) {
      var handler = this.handlers.get(tmp[i]);

      if (handler == null) {
        handler = this.graph.createHandler(state);
        this.fireEvent(new mxEventObject(mxEvent.ADD, 'state', state));
        this.handlers.put(tmp[i], handler);
      } else {
        handler.updateParentHighlight();
      }
    }
  }
};

/**
 * Function: isHandlerActive
 *
 * Returns true if the given handler is active and should not be redrawn.
 */
mxSelectionCellsHandler.prototype.isHandlerActive = function (handler) {
  return handler.index != null;
};

/**
 * Function: updateHandler
 *
 * Updates the handler for the given shape if one exists.
 */
mxSelectionCellsHandler.prototype.updateHandler = function (state) {
  var handler = this.handlers.remove(state.cell);

  if (handler != null) {
    // Transfers the current state to the new handler
    var index = handler.index;
    var x = handler.startX;
    var y = handler.startY;

    handler.destroy();
    handler = this.graph.createHandler(state);

    if (handler != null) {
      this.handlers.put(state.cell, handler);

      if (index != null && x != null && y != null) {
        handler.start(x, y, index);
      }
    }
  }
};

/**
 * Function: mouseDown
 *
 * Redirects the given event to the handlers.
 */
mxSelectionCellsHandler.prototype.mouseDown = function (sender, me) {
  if (this.graph.isEnabled() && this.isEnabled()) {
    var args = [sender, me];

    this.handlers.visit(function (key, handler) {
      handler.mouseDown.apply(handler, args);
    });
  }
};

/**
 * Function: mouseMove
 *
 * Redirects the given event to the handlers.
 */
mxSelectionCellsHandler.prototype.mouseMove = function (sender, me) {
  if (this.graph.isEnabled() && this.isEnabled()) {
    var args = [sender, me];

    this.handlers.visit(function (key, handler) {
      handler.mouseMove.apply(handler, args);
    });
  }
};

/**
 * Function: mouseUp
 *
 * Redirects the given event to the handlers.
 */
mxSelectionCellsHandler.prototype.mouseUp = function (sender, me) {
  if (this.graph.isEnabled() && this.isEnabled()) {
    var args = [sender, me];

    this.handlers.visit(function (key, handler) {
      handler.mouseUp.apply(handler, args);
    });
  }
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes.
 */
mxSelectionCellsHandler.prototype.destroy = function () {
  this.graph.removeMouseListener(this);

  if (this.refreshHandler != null) {
    this.graph.getSelectionModel().removeListener(this.refreshHandler);
    this.graph.getModel().removeListener(this.refreshHandler);
    this.graph.getView().removeListener(this.refreshHandler);
    this.refreshHandler = null;
  }
};

/**
 * handler/mxConnectionHandler.js
 */
/**
 * Copyright (c) 2006-2016, JGraph Ltd
 * Copyright (c) 2006-2016, Gaudenz Alder
 */
/**
 * Class: mxConnectionHandler
 *
 * Graph event handler that creates new connections. Uses <mxTerminalMarker>
 * for finding and highlighting the source and target vertices and
 * <factoryMethod> to create the edge instance. This handler is built-into
 * <mxGraph.connectionHandler> and enabled using <mxGraph.setConnectable>.
 *
 * Example:
 *
 * (code)
 * new mxConnectionHandler(graph, function(source, target, style)
 * {
 *   edge = new mxCell('', new mxGeometry());
 *   edge.setEdge(true);
 *   edge.setStyle(style);
 *   edge.geometry.relative = true;
 *   return edge;
 * });
 * (end)
 *
 * Here is an alternative solution that just sets a specific user object for
 * new edges by overriding <insertEdge>.
 *
 * (code)
 * mxConnectionHandlerInsertEdge = mxConnectionHandler.prototype.insertEdge;
 * mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
 * {
 *   value = 'Test';
 *
 *   return mxConnectionHandlerInsertEdge.apply(this, arguments);
 * };
 * (end)
 *
 * Using images to trigger connections:
 *
 * This handler uses mxTerminalMarker to find the source and target cell for
 * the new connection and creates a new edge using <connect>. The new edge is
 * created using <createEdge> which in turn uses <factoryMethod> or creates a
 * new default edge.
 *
 * The handler uses a "highlight-paradigm" for indicating if a cell is being
 * used as a source or target terminal, as seen in other diagramming products.
 * In order to allow both, moving and connecting cells at the same time,
 * <mxConstants.DEFAULT_HOTSPOT> is used in the handler to determine the hotspot
 * of a cell, that is, the region of the cell which is used to trigger a new
 * connection. The constant is a value between 0 and 1 that specifies the
 * amount of the width and height around the center to be used for the hotspot
 * of a cell and its default value is 0.5. In addition,
 * <mxConstants.MIN_HOTSPOT_SIZE> defines the minimum number of pixels for the
 * width and height of the hotspot.
 *
 * This solution, while standards compliant, may be somewhat confusing because
 * there is no visual indicator for the hotspot and the highlight is seen to
 * switch on and off while the mouse is being moved in and out. Furthermore,
 * this paradigm does not allow to create different connections depending on
 * the highlighted hotspot as there is only one hotspot per cell and it
 * normally does not allow cells to be moved and connected at the same time as
 * there is no clear indication of the connectable area of the cell.
 *
 * To come across these issues, the handle has an additional <createIcons> hook
 * with a default implementation that allows to create one icon to be used to
 * trigger new connections. If this icon is specified, then new connections can
 * only be created if the image is clicked while the cell is being highlighted.
 * The <createIcons> hook may be overridden to create more than one
 * <mxImageShape> for creating new connections, but the default implementation
 * supports one image and is used as follows:
 *
 * In order to display the "connect image" whenever the mouse is over the cell,
 * an DEFAULT_HOTSPOT of 1 should be used:
 *
 * (code)
 * mxConstants.DEFAULT_HOTSPOT = 1;
 * (end)
 *
 * In order to avoid confusion with the highlighting, the highlight color
 * should not be used with a connect image:
 *
 * (code)
 * mxConstants.HIGHLIGHT_COLOR = null;
 * (end)
 *
 * To install the image, the connectImage field of the mxConnectionHandler must
 * be assigned a new <mxImage> instance:
 *
 * (code)
 * mxConnectionHandler.prototype.connectImage = new mxImage('images/green-dot.gif', 14, 14);
 * (end)
 *
 * This will use the green-dot.gif with a width and height of 14 pixels as the
 * image to trigger new connections. In createIcons the icon field of the
 * handler will be set in order to remember the icon that has been clicked for
 * creating the new connection. This field will be available under selectedIcon
 * in the connect method, which may be overridden to take the icon that
 * triggered the new connection into account. This is useful if more than one
 * icon may be used to create a connection.
 *
 * Group: Events
 *
 * Event: mxEvent.START
 *
 * Fires when a new connection is being created by the user. The <code>state</code>
 * property contains the state of the source cell.
 *
 * Event: mxEvent.CONNECT
 *
 * Fires between begin- and endUpdate in <connect>. The <code>cell</code>
 * property contains the inserted edge, the <code>event</code> and <code>target</code>
 * properties contain the respective arguments that were passed to <connect> (where
 * target corresponds to the dropTarget argument). Finally, the <code>terminal</code>
 * property corresponds to the target argument in <connect> or the clone of the source
 * terminal if <createTarget> is enabled.
 *
 * Note that the target is the cell under the mouse where the mouse button was released.
 * Depending on the logic in the handler, this doesn't necessarily have to be the target
 * of the inserted edge. To print the source, target or any optional ports IDs that the
 * edge is connected to, the following code can be used. To get more details about the
 * actual connection point, <mxGraph.getConnectionConstraint> can be used. To resolve
 * the port IDs, use <mxGraphModel.getCell>.
 *
 * (code)
 * graph.connectionHandler.addListener(mxEvent.CONNECT, function(sender, evt)
 * {
 *   var edge = evt.getProperty('cell');
 *   var source = graph.getModel().getTerminal(edge, true);
 *   var target = graph.getModel().getTerminal(edge, false);
 *
 *   var style = graph.getCellStyle(edge);
 *   var sourcePortId = style[mxConstants.STYLE_SOURCE_PORT];
 *   var targetPortId = style[mxConstants.STYLE_TARGET_PORT];
 *
 *   mxLog.show();
 *   mxLog.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId);
 * });
 * (end)
 *
 * Event: mxEvent.RESET
 *
 * Fires when the <reset> method is invoked.
 *
 * Constructor: mxConnectionHandler
 *
 * Constructs an event handler that connects vertices using the specified
 * factory method to create the new edges. Modify
 * <mxConstants.ACTIVE_REGION> to setup the region on a cell which triggers
 * the creation of a new connection or use connect icons as explained
 * above.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 * factoryMethod - Optional function to create the edge. The function takes
 * the source and target <mxCell> as the first and second argument and an
 * optional cell style from the preview as the third argument. It returns
 * the <mxCell> that represents the new edge.
 */
function mxConnectionHandler(graph, factoryMethod) {
  mxEventSource.call(this);

  if (graph != null) {
    this.graph = graph;
    this.factoryMethod = factoryMethod;
    this.init();

    // Handles escape keystrokes
    this.escapeHandler = mxUtils.bind(this, function (sender, evt) {
      this.reset();
    });

    this.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
  }
}

/**
 * Extends mxEventSource.
 */
mxUtils.extend(mxConnectionHandler, mxEventSource);

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxConnectionHandler.prototype.graph = null;

/**
 * Variable: factoryMethod
 *
 * Function that is used for creating new edges. The function takes the
 * source and target <mxCell> as the first and second argument and returns
 * a new <mxCell> that represents the edge. This is used in <createEdge>.
 */
mxConnectionHandler.prototype.factoryMethod = true;

/**
 * Variable: moveIconFront
 *
 * Specifies if icons should be displayed inside the graph container instead
 * of the overlay pane. This is used for HTML labels on vertices which hide
 * the connect icon. This has precendence over <moveIconBack> when set
 * to true. Default is false.
 */
mxConnectionHandler.prototype.moveIconFront = false;

/**
 * Variable: moveIconBack
 *
 * Specifies if icons should be moved to the back of the overlay pane. This can
 * be set to true if the icons of the connection handler conflict with other
 * handles, such as the vertex label move handle. Default is false.
 */
mxConnectionHandler.prototype.moveIconBack = false;

/**
 * Variable: connectImage
 *
 * <mxImage> that is used to trigger the creation of a new connection. This
 * is used in <createIcons>. Default is null.
 */
mxConnectionHandler.prototype.connectImage = null;

/**
 * Variable: targetConnectImage
 *
 * Specifies if the connect icon should be centered on the target state
 * while connections are being previewed. Default is false.
 */
mxConnectionHandler.prototype.targetConnectImage = false;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxConnectionHandler.prototype.enabled = true;

/**
 * Variable: select
 *
 * Specifies if new edges should be selected. Default is true.
 */
mxConnectionHandler.prototype.select = true;

/**
 * Variable: createTarget
 *
 * Specifies if <createTargetVertex> should be called if no target was under the
 * mouse for the new connection. Setting this to true means the connection
 * will be drawn as valid if no target is under the mouse, and
 * <createTargetVertex> will be called before the connection is created between
 * the source cell and the newly created vertex in <createTargetVertex>, which
 * can be overridden to create a new target. Default is false.
 */
mxConnectionHandler.prototype.createTarget = false;

/**
 * Variable: marker
 *
 * Holds the <mxTerminalMarker> used for finding source and target cells.
 */
mxConnectionHandler.prototype.marker = null;

/**
 * Variable: constraintHandler
 *
 * Holds the <mxConstraintHandler> used for drawing and highlighting
 * constraints.
 */
mxConnectionHandler.prototype.constraintHandler = null;

/**
 * Variable: error
 *
 * Holds the current validation error while connections are being created.
 */
mxConnectionHandler.prototype.error = null;

/**
 * Variable: waypointsEnabled
 *
 * Specifies if single clicks should add waypoints on the new edge. Default is
 * false.
 */
mxConnectionHandler.prototype.waypointsEnabled = false;

/**
 * Variable: ignoreMouseDown
 *
 * Specifies if the connection handler should ignore the state of the mouse
 * button when highlighting the source. Default is false, that is, the
 * handler only highlights the source if no button is being pressed.
 */
mxConnectionHandler.prototype.ignoreMouseDown = false;

/**
 * Variable: first
 *
 * Holds the <mxPoint> where the mouseDown took place while the handler is
 * active.
 */
mxConnectionHandler.prototype.first = null;

/**
 * Variable: connectIconOffset
 *
 * Holds the offset for connect icons during connection preview.
 * Default is mxPoint(0, <mxConstants.TOOLTIP_VERTICAL_OFFSET>).
 * Note that placing the icon under the mouse pointer with an
 * offset of (0,0) will affect hit detection.
 */
mxConnectionHandler.prototype.connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET);

/**
 * Variable: edgeState
 *
 * Optional <mxCellState> that represents the preview edge while the
 * handler is active. This is created in <createEdgeState>.
 */
mxConnectionHandler.prototype.edgeState = null;

/**
 * Variable: changeHandler
 *
 * Holds the change event listener for later removal.
 */
mxConnectionHandler.prototype.changeHandler = null;

/**
 * Variable: drillHandler
 *
 * Holds the drill event listener for later removal.
 */
mxConnectionHandler.prototype.drillHandler = null;

/**
 * Variable: mouseDownCounter
 *
 * Counts the number of mouseDown events since the start. The initial mouse
 * down event counts as 1.
 */
mxConnectionHandler.prototype.mouseDownCounter = 0;

/**
 * Variable: movePreviewAway
 *
 * Switch to enable moving the preview away from the mousepointer. This is required in browsers
 * where the preview cannot be made transparent to events and if the built-in hit detection on
 * the HTML elements in the page should be used. Default is the value of <mxClient.IS_VML>.
 */
mxConnectionHandler.prototype.movePreviewAway = mxClient.IS_VML;

/**
 * Variable: outlineConnect
 *
 * Specifies if connections to the outline of a highlighted target should be
 * enabled. This will allow to place the connection point along the outline of
 * the highlighted target. Default is false.
 */
mxConnectionHandler.prototype.outlineConnect = false;

/**
 * Variable: livePreview
 *
 * Specifies if the actual shape of the edge state should be used for the preview.
 * Default is false. (Ignored if no edge state is created in <createEdgeState>.)
 */
mxConnectionHandler.prototype.livePreview = false;

/**
 * Variable: cursor
 *
 * Specifies the cursor to be used while the handler is active. Default is null.
 */
mxConnectionHandler.prototype.cursor = null;

/**
 * Variable: insertBeforeSource
 *
 * Specifies if new edges should be inserted before the source vertex in the
 * cell hierarchy. Default is false for backwards compatibility.
 */
mxConnectionHandler.prototype.insertBeforeSource = false;

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxConnectionHandler.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 *
 * Parameters:
 *
 * enabled - Boolean that specifies the new enabled state.
 */
mxConnectionHandler.prototype.setEnabled = function (enabled) {
  this.enabled = enabled;
};

/**
 * Function: isInsertBefore
 *
 * Returns <insertBeforeSource> for non-loops and false for loops.
 *
 * Parameters:
 *
 * edge - <mxCell> that represents the edge to be inserted.
 * source - <mxCell> that represents the source terminal.
 * target - <mxCell> that represents the target terminal.
 * evt - Mousedown event of the connect gesture.
 * dropTarget - <mxCell> that represents the cell under the mouse when it was
 * released.
 */
mxConnectionHandler.prototype.isInsertBefore = function (edge, source, target, evt, dropTarget) {
  return this.insertBeforeSource && source != target;
};

/**
 * Function: isCreateTarget
 *
 * Returns <createTarget>.
 *
 * Parameters:
 *
 * evt - Current active native pointer event.
 */
mxConnectionHandler.prototype.isCreateTarget = function (evt) {
  return this.createTarget;
};

/**
 * Function: setCreateTarget
 *
 * Sets <createTarget>.
 */
mxConnectionHandler.prototype.setCreateTarget = function (value) {
  this.createTarget = value;
};

/**
 * Function: createShape
 *
 * Creates the preview shape for new connections.
 */
mxConnectionHandler.prototype.createShape = function () {
  // Creates the edge preview
  var shape =
    this.livePreview && this.edgeState != null
      ? this.graph.cellRenderer.createShape(this.edgeState)
      : new mxPolyline([], mxConstants.INVALID_COLOR);
  shape.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
  shape.scale = this.graph.view.scale;
  shape.pointerEvents = false;
  shape.isDashed = true;
  shape.init(this.graph.getView().getOverlayPane());
  mxEvent.redirectMouseEvents(shape.node, this.graph, null);

  return shape;
};

/**
 * Function: init
 *
 * Initializes the shapes required for this connection handler. This should
 * be invoked if <mxGraph.container> is assigned after the connection
 * handler has been created.
 */
mxConnectionHandler.prototype.init = function () {
  this.graph.addMouseListener(this);
  this.marker = this.createMarker();
  this.constraintHandler = new mxConstraintHandler(this.graph);

  // Redraws the icons if the graph changes
  this.changeHandler = mxUtils.bind(this, function (sender) {
    if (this.iconState != null) {
      this.iconState = this.graph.getView().getState(this.iconState.cell);
    }

    if (this.iconState != null) {
      this.redrawIcons(this.icons, this.iconState);
      this.constraintHandler.reset();
    } else if (this.previous != null && this.graph.view.getState(this.previous.cell) == null) {
      this.reset();
    }
  });

  this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
  this.graph.getView().addListener(mxEvent.SCALE, this.changeHandler);
  this.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler);
  this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler);

  // Removes the icon if we step into/up or start editing
  this.drillHandler = mxUtils.bind(this, function (sender) {
    this.reset();
  });

  this.graph.addListener(mxEvent.START_EDITING, this.drillHandler);
  this.graph.getView().addListener(mxEvent.DOWN, this.drillHandler);
  this.graph.getView().addListener(mxEvent.UP, this.drillHandler);
};

/**
 * Function: isConnectableCell
 *
 * Returns true if the given cell is connectable. This is a hook to
 * disable floating connections. This implementation returns true.
 */
mxConnectionHandler.prototype.isConnectableCell = function (cell) {
  return true;
};

/**
 * Function: createMarker
 *
 * Creates and returns the <mxCellMarker> used in <marker>.
 */
mxConnectionHandler.prototype.createMarker = function () {
  var marker = new mxCellMarker(this.graph);
  marker.hotspotEnabled = true;

  // Overrides to return cell at location only if valid (so that
  // there is no highlight for invalid cells)
  marker.getCell = mxUtils.bind(this, function (me) {
    var cell = mxCellMarker.prototype.getCell.apply(marker, arguments);
    this.error = null;

    // Checks for cell at preview point (with grid)
    if (cell == null && this.currentPoint != null) {
      cell = this.graph.getCellAt(this.currentPoint.x, this.currentPoint.y);
    }

    // Uses connectable parent vertex if one exists
    if (cell != null && !this.graph.isCellConnectable(cell)) {
      var parent = this.graph.getModel().getParent(cell);

      if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) {
        cell = parent;
      }
    }

    if (
      (this.graph.isSwimlane(cell) &&
        this.currentPoint != null &&
        this.graph.hitsSwimlaneContent(cell, this.currentPoint.x, this.currentPoint.y)) ||
      !this.isConnectableCell(cell)
    ) {
      cell = null;
    }

    if (cell != null) {
      if (this.isConnecting()) {
        if (this.previous != null) {
          this.error = this.validateConnection(this.previous.cell, cell);

          if (this.error != null && this.error.length == 0) {
            cell = null;

            // Enables create target inside groups
            if (this.isCreateTarget(me.getEvent())) {
              this.error = null;
            }
          }
        }
      } else if (!this.isValidSource(cell, me)) {
        cell = null;
      }
    } else if (this.isConnecting() && !this.isCreateTarget(me.getEvent()) && !this.graph.allowDanglingEdges) {
      this.error = '';
    }

    return cell;
  });

  // Sets the highlight color according to validateConnection
  marker.isValidState = mxUtils.bind(this, function (state) {
    if (this.isConnecting()) {
      return this.error == null;
    } else {
      return mxCellMarker.prototype.isValidState.apply(marker, arguments);
    }
  });

  // Overrides to use marker color only in highlight mode or for
  // target selection
  marker.getMarkerColor = mxUtils.bind(this, function (evt, state, isValid) {
    return this.connectImage == null || this.isConnecting()
      ? mxCellMarker.prototype.getMarkerColor.apply(marker, arguments)
      : null;
  });

  // Overrides to use hotspot only for source selection otherwise
  // intersects always returns true when over a cell
  marker.intersects = mxUtils.bind(this, function (state, evt) {
    if (this.connectImage != null || this.isConnecting()) {
      return true;
    }

    return mxCellMarker.prototype.intersects.apply(marker, arguments);
  });

  return marker;
};

/**
 * Function: start
 *
 * Starts a new connection for the given state and coordinates.
 */
mxConnectionHandler.prototype.start = function (state, x, y, edgeState) {
  this.previous = state;
  this.first = new mxPoint(x, y);
  this.edgeState = edgeState != null ? edgeState : this.createEdgeState(null);

  // Marks the source state
  this.marker.currentColor = this.marker.validColor;
  this.marker.markedState = state;
  this.marker.mark();

  this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
};

/**
 * Function: isConnecting
 *
 * Returns true if the source terminal has been clicked and a new
 * connection is currently being previewed.
 */
mxConnectionHandler.prototype.isConnecting = function () {
  return this.first != null && this.shape != null;
};

/**
 * Function: isValidSource
 *
 * Returns <mxGraph.isValidSource> for the given source terminal.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the source terminal.
 * me - <mxMouseEvent> that is associated with this call.
 */
mxConnectionHandler.prototype.isValidSource = function (cell, me) {
  return this.graph.isValidSource(cell);
};

/**
 * Function: isValidTarget
 *
 * Returns true. The call to <mxGraph.isValidTarget> is implicit by calling
 * <mxGraph.getEdgeValidationError> in <validateConnection>. This is an
 * additional hook for disabling certain targets in this specific handler.
 *
 * Parameters:
 *
 * cell - <mxCell> that represents the target terminal.
 */
mxConnectionHandler.prototype.isValidTarget = function (cell) {
  return true;
};

/**
 * Function: validateConnection
 *
 * Returns the error message or an empty string if the connection for the
 * given source target pair is not valid. Otherwise it returns null. This
 * implementation uses <mxGraph.getEdgeValidationError>.
 *
 * Parameters:
 *
 * source - <mxCell> that represents the source terminal.
 * target - <mxCell> that represents the target terminal.
 */
mxConnectionHandler.prototype.validateConnection = function (source, target) {
  if (!this.isValidTarget(target)) {
    return '';
  }

  return this.graph.getEdgeValidationError(null, source, target);
};

/**
 * Function: getConnectImage
 *
 * Hook to return the <mxImage> used for the connection icon of the given
 * <mxCellState>. This implementation returns <connectImage>.
 *
 * Parameters:
 *
 * state - <mxCellState> whose connect image should be returned.
 */
mxConnectionHandler.prototype.getConnectImage = function (state) {
  return this.connectImage;
};

/**
 * Function: isMoveIconToFrontForState
 *
 * Returns true if the state has a HTML label in the graph's container, otherwise
 * it returns <moveIconFront>.
 *
 * Parameters:
 *
 * state - <mxCellState> whose connect icons should be returned.
 */
mxConnectionHandler.prototype.isMoveIconToFrontForState = function (state) {
  if (state.text != null && state.text.node.parentNode == this.graph.container) {
    return true;
  }

  return this.moveIconFront;
};

/**
 * Function: createIcons
 *
 * Creates the array <mxImageShapes> that represent the connect icons for
 * the given <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> whose connect icons should be returned.
 */
mxConnectionHandler.prototype.createIcons = function (state) {
  var image = this.getConnectImage(state);

  if (image != null && state != null) {
    this.iconState = state;
    var icons = [];

    // Cannot use HTML for the connect icons because the icon receives all
    // mouse move events in IE, must use VML and SVG instead even if the
    // connect-icon appears behind the selection border and the selection
    // border consumes the events before the icon gets a chance
    var bounds = new mxRectangle(0, 0, image.width, image.height);
    var icon = new mxImageShape(bounds, image.src, null, null, 0);
    icon.preserveImageAspect = false;

    if (this.isMoveIconToFrontForState(state)) {
      icon.dialect = mxConstants.DIALECT_STRICTHTML;
      icon.init(this.graph.container);
    } else {
      icon.dialect = this.graph.dialect == mxConstants.DIALECT_SVG ? mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML;
      icon.init(this.graph.getView().getOverlayPane());

      // Move the icon back in the overlay pane
      if (this.moveIconBack && icon.node.previousSibling != null) {
        icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
      }
    }

    icon.node.style.cursor = mxConstants.CURSOR_CONNECT;

    // Events transparency
    var getState = mxUtils.bind(this, function () {
      return this.currentState != null ? this.currentState : state;
    });

    // Updates the local icon before firing the mouse down event.
    var mouseDown = mxUtils.bind(this, function (evt) {
      if (!mxEvent.isConsumed(evt)) {
        this.icon = icon;
        this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, getState()));
      }
    });

    mxEvent.redirectMouseEvents(icon.node, this.graph, getState, mouseDown);

    icons.push(icon);
    this.redrawIcons(icons, this.iconState);

    return icons;
  }

  return null;
};

/**
 * Function: redrawIcons
 *
 * Redraws the given array of <mxImageShapes>.
 *
 * Parameters:
 *
 * icons - Optional array of <mxImageShapes> to be redrawn.
 */
mxConnectionHandler.prototype.redrawIcons = function (icons, state) {
  if (icons != null && icons[0] != null && state != null) {
    var pos = this.getIconPosition(icons[0], state);
    icons[0].bounds.x = pos.x;
    icons[0].bounds.y = pos.y;
    icons[0].redraw();
  }
};

/**
 * Function: getIconPosition
 *
 * Returns the center position of the given icon.
 *
 * Parameters:
 *
 * icon - The connect icon of <mxImageShape> with the mouse.
 * state - <mxCellState> under the mouse.
 */
mxConnectionHandler.prototype.getIconPosition = function (icon, state) {
  var scale = this.graph.getView().scale;
  var cx = state.getCenterX();
  var cy = state.getCenterY();

  if (this.graph.isSwimlane(state.cell)) {
    var size = this.graph.getStartSize(state.cell);

    cx = size.width != 0 ? state.x + (size.width * scale) / 2 : cx;
    cy = size.height != 0 ? state.y + (size.height * scale) / 2 : cy;

    var alpha = mxUtils.toRadians(mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION) || 0);

    if (alpha != 0) {
      var cos = Math.cos(alpha);
      var sin = Math.sin(alpha);
      var ct = new mxPoint(state.getCenterX(), state.getCenterY());
      var pt = mxUtils.getRotatedPoint(new mxPoint(cx, cy), cos, sin, ct);
      cx = pt.x;
      cy = pt.y;
    }
  }

  return new mxPoint(cx - icon.bounds.width / 2, cy - icon.bounds.height / 2);
};

/**
 * Function: destroyIcons
 *
 * Destroys the connect icons and resets the respective state.
 */
mxConnectionHandler.prototype.destroyIcons = function () {
  if (this.icons != null) {
    for (var i = 0; i < this.icons.length; i++) {
      this.icons[i].destroy();
    }

    this.icons = null;
    this.icon = null;
    this.selectedIcon = null;
    this.iconState = null;
  }
};

/**
 * Function: isStartEvent
 *
 * Returns true if the given mouse down event should start this handler. The
 * This implementation returns true if the event does not force marquee
 * selection, and the currentConstraint and currentFocus of the
 * <constraintHandler> are not null, or <previous> and <error> are not null and
 * <icons> is null or <icons> and <icon> are not null.
 */
mxConnectionHandler.prototype.isStartEvent = function (me) {
  return (
    (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) ||
    (this.previous != null && this.error == null && (this.icons == null || (this.icons != null && this.icon != null)))
  );
};

/**
 * Function: mouseDown
 *
 * Handles the event by initiating a new connection.
 */
mxConnectionHandler.prototype.mouseDown = function (sender, me) {
  this.mouseDownCounter++;

  if (this.isEnabled() && this.graph.isEnabled() && !me.isConsumed() && !this.isConnecting() && this.isStartEvent(me)) {
    if (
      this.constraintHandler.currentConstraint != null &&
      this.constraintHandler.currentFocus != null &&
      this.constraintHandler.currentPoint != null
    ) {
      this.sourceConstraint = this.constraintHandler.currentConstraint;
      this.previous = this.constraintHandler.currentFocus;
      this.first = this.constraintHandler.currentPoint.clone();
    } else {
      // Stores the location of the initial mousedown
      this.first = new mxPoint(me.getGraphX(), me.getGraphY());
    }

    this.edgeState = this.createEdgeState(me);
    this.mouseDownCounter = 1;

    if (this.waypointsEnabled && this.shape == null) {
      this.waypoints = null;
      this.shape = this.createShape();

      if (this.edgeState != null) {
        this.shape.apply(this.edgeState);
      }
    }

    // Stores the starting point in the geometry of the preview
    if (this.previous == null && this.edgeState != null) {
      var pt = this.graph.getPointForEvent(me.getEvent());
      this.edgeState.cell.geometry.setTerminalPoint(pt, true);
    }

    this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));

    me.consume();
  }

  this.selectedIcon = this.icon;
  this.icon = null;
};

/**
 * Function: isImmediateConnectSource
 *
 * Returns true if a tap on the given source state should immediately start
 * connecting. This implementation returns true if the state is not movable
 * in the graph.
 */
mxConnectionHandler.prototype.isImmediateConnectSource = function (state) {
  return !this.graph.isCellMovable(state.cell);
};

/**
 * Function: createEdgeState
 *
 * Hook to return an <mxCellState> which may be used during the preview.
 * This implementation returns null.
 *
 * Use the following code to create a preview for an existing edge style:
 *
 * (code)
 * graph.connectionHandler.createEdgeState = function(me)
 * {
 *   var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle');
 *
 *   return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));
 * };
 * (end)
 */
mxConnectionHandler.prototype.createEdgeState = function (me) {
  return null;
};

/**
 * Function: isOutlineConnectEvent
 *
 * Returns true if <outlineConnect> is true and the source of the event is the outline shape
 * or shift is pressed.
 */
mxConnectionHandler.prototype.isOutlineConnectEvent = function (me) {
  var offset = mxUtils.getOffset(this.graph.container);
  var evt = me.getEvent();

  var clientX = mxEvent.getClientX(evt);
  var clientY = mxEvent.getClientY(evt);

  var doc = document.documentElement;
  var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
  var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);

  var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;
  var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;

  return (
    this.outlineConnect &&
    !mxEvent.isShiftDown(me.getEvent()) &&
    (me.isSource(this.marker.highlight.shape) ||
      (mxEvent.isAltDown(me.getEvent()) && me.getState() != null) ||
      this.marker.highlight.isHighlightAt(clientX, clientY) ||
      ((gridX != clientX || gridY != clientY) &&
        me.getState() == null &&
        this.marker.highlight.isHighlightAt(gridX, gridY)))
  );
};

/**
 * Function: updateCurrentState
 *
 * Updates the current state for a given mouse move event by using
 * the <marker>.
 */
mxConnectionHandler.prototype.updateCurrentState = function (me, point) {
  this.constraintHandler.update(
    me,
    this.first == null,
    false,
    this.first == null || me.isSource(this.marker.highlight.shape) ? null : point
  );

  if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) {
    // Handles special case where grid is large and connection point is at actual point in which
    // case the outline is not followed as long as we're < gridSize / 2 away from that point
    if (
      this.marker.highlight != null &&
      this.marker.highlight.state != null &&
      this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell
    ) {
      // Direct repaint needed if cell already highlighted
      if (this.marker.highlight.shape.stroke != 'transparent') {
        this.marker.highlight.shape.stroke = 'transparent';
        this.marker.highlight.repaint();
      }
    } else {
      this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent');
    }

    // Updates validation state
    if (this.previous != null) {
      this.error = this.validateConnection(this.previous.cell, this.constraintHandler.currentFocus.cell);

      if (this.error == null) {
        this.currentState = this.constraintHandler.currentFocus;
      }

      if (this.error != null || (this.currentState != null && !this.isCellEnabled(this.currentState.cell))) {
        this.constraintHandler.reset();
      }
    }
  } else {
    if (this.graph.isIgnoreTerminalEvent(me.getEvent())) {
      this.marker.reset();
      this.currentState = null;
    } else {
      this.marker.process(me);
      this.currentState = this.marker.getValidState();
    }

    if (this.currentState != null && !this.isCellEnabled(this.currentState.cell)) {
      this.constraintHandler.reset();
      this.marker.reset();
      this.currentState = null;
    }

    var outline = this.isOutlineConnectEvent(me);

    if (this.currentState != null && outline) {
      // Handles special case where mouse is on outline away from actual end point
      // in which case the grid is ignored and mouse point is used instead
      if (me.isSource(this.marker.highlight.shape)) {
        point = new mxPoint(me.getGraphX(), me.getGraphY());
      }

      var constraint = this.graph.getOutlineConstraint(point, this.currentState, me);
      this.constraintHandler.setFocus(me, this.currentState, false);
      this.constraintHandler.currentConstraint = constraint;
      this.constraintHandler.currentPoint = point;
    }

    if (this.outlineConnect) {
      if (this.marker.highlight != null && this.marker.highlight.shape != null) {
        var s = this.graph.view.scale;

        if (this.constraintHandler.currentConstraint != null && this.constraintHandler.currentFocus != null) {
          this.marker.highlight.shape.stroke = mxConstants.OUTLINE_HIGHLIGHT_COLOR;
          this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;
          this.marker.highlight.repaint();
        } else if (this.marker.hasValidState()) {
          // Handles special case where actual end point of edge and current mouse point
          // are not equal (due to grid snapping) and there is no hit on shape or highlight
          // but ignores cases where parent is used for non-connectable child cells
          if (this.graph.isCellConnectable(me.getCell()) && this.marker.getValidState() != me.getState()) {
            this.marker.highlight.shape.stroke = 'transparent';
            this.currentState = null;
          } else {
            this.marker.highlight.shape.stroke = mxConstants.DEFAULT_VALID_COLOR;
          }

          this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;
          this.marker.highlight.repaint();
        }
      }
    }
  }
};

/**
 * Function: isCellEnabled
 *
 * Returns true if the given cell allows new connections to be created. This implementation
 * always returns true.
 */
mxConnectionHandler.prototype.isCellEnabled = function (cell) {
  return true;
};

/**
 * Function: convertWaypoint
 *
 * Converts the given point from screen coordinates to model coordinates.
 */
mxConnectionHandler.prototype.convertWaypoint = function (point) {
  var scale = this.graph.getView().getScale();
  var tr = this.graph.getView().getTranslate();

  point.x = point.x / scale - tr.x;
  point.y = point.y / scale - tr.y;
};

/**
 * Function: snapToPreview
 *
 * Called to snap the given point to the current preview. This snaps to the
 * first point of the preview if alt is not pressed.
 */
mxConnectionHandler.prototype.snapToPreview = function (me, point) {
  if (!mxEvent.isAltDown(me.getEvent()) && this.previous != null) {
    var tol = (this.graph.gridSize * this.graph.view.scale) / 2;
    var tmp =
      this.sourceConstraint != null ? this.first : new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());

    if (Math.abs(tmp.x - me.getGraphX()) < tol) {
      point.x = tmp.x;
    }

    if (Math.abs(tmp.y - me.getGraphY()) < tol) {
      point.y = tmp.y;
    }
  }
};

/**
 * Function: mouseMove
 *
 * Handles the event by updating the preview edge or by highlighting
 * a possible source or target terminal.
 */
mxConnectionHandler.prototype.mouseMove = function (sender, me) {
  if (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown)) {
    // Handles special case when handler is disabled during highlight
    if (!this.isEnabled() && this.currentState != null) {
      this.destroyIcons();
      this.currentState = null;
    }

    var view = this.graph.getView();
    var scale = view.scale;
    var tr = view.translate;
    var point = new mxPoint(me.getGraphX(), me.getGraphY());
    this.error = null;

    if (this.graph.isGridEnabledEvent(me.getEvent())) {
      point = new mxPoint(
        (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale,
        (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale
      );
    }

    this.snapToPreview(me, point);
    this.currentPoint = point;

    if (
      (this.first != null || (this.isEnabled() && this.graph.isEnabled())) &&
      (this.shape != null ||
        this.first == null ||
        Math.abs(me.getGraphX() - this.first.x) > this.graph.tolerance ||
        Math.abs(me.getGraphY() - this.first.y) > this.graph.tolerance)
    ) {
      this.updateCurrentState(me, point);
    }

    if (this.first != null) {
      var constraint = null;
      var current = point;

      // Uses the current point from the constraint handler if available
      if (
        this.constraintHandler.currentConstraint != null &&
        this.constraintHandler.currentFocus != null &&
        this.constraintHandler.currentPoint != null
      ) {
        constraint = this.constraintHandler.currentConstraint;
        current = this.constraintHandler.currentPoint.clone();
      } else if (
        this.previous != null &&
        !this.graph.isIgnoreTerminalEvent(me.getEvent()) &&
        mxEvent.isShiftDown(me.getEvent())
      ) {
        if (Math.abs(this.previous.getCenterX() - point.x) < Math.abs(this.previous.getCenterY() - point.y)) {
          point.x = this.previous.getCenterX();
        } else {
          point.y = this.previous.getCenterY();
        }
      }

      var pt2 = this.first;

      // Moves the connect icon with the mouse
      if (this.selectedIcon != null) {
        var w = this.selectedIcon.bounds.width;
        var h = this.selectedIcon.bounds.height;

        if (this.currentState != null && this.targetConnectImage) {
          var pos = this.getIconPosition(this.selectedIcon, this.currentState);
          this.selectedIcon.bounds.x = pos.x;
          this.selectedIcon.bounds.y = pos.y;
        } else {
          var bounds = new mxRectangle(
            me.getGraphX() + this.connectIconOffset.x,
            me.getGraphY() + this.connectIconOffset.y,
            w,
            h
          );
          this.selectedIcon.bounds = bounds;
        }

        this.selectedIcon.redraw();
      }

      // Uses edge state to compute the terminal points
      if (this.edgeState != null) {
        this.updateEdgeState(current, constraint);
        current = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1];
        pt2 = this.edgeState.absolutePoints[0];
      } else {
        if (this.currentState != null) {
          if (this.constraintHandler.currentConstraint == null) {
            var tmp = this.getTargetPerimeterPoint(this.currentState, me);

            if (tmp != null) {
              current = tmp;
            }
          }
        }

        // Computes the source perimeter point
        if (this.sourceConstraint == null && this.previous != null) {
          var next = this.waypoints != null && this.waypoints.length > 0 ? this.waypoints[0] : current;
          var tmp = this.getSourcePerimeterPoint(this.previous, next, me);

          if (tmp != null) {
            pt2 = tmp;
          }
        }
      }

      // Makes sure the cell under the mousepointer can be detected
      // by moving the preview shape away from the mouse. This
      // makes sure the preview shape does not prevent the detection
      // of the cell under the mousepointer even for slow gestures.
      if (this.currentState == null && this.movePreviewAway) {
        var tmp = pt2;

        if (this.edgeState != null && this.edgeState.absolutePoints.length >= 2) {
          var tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2];

          if (tmp2 != null) {
            tmp = tmp2;
          }
        }

        var dx = current.x - tmp.x;
        var dy = current.y - tmp.y;

        var len = Math.sqrt(dx * dx + dy * dy);

        if (len == 0) {
          return;
        }

        // Stores old point to reuse when creating edge
        this.originalPoint = current.clone();
        current.x -= (dx * 4) / len;
        current.y -= (dy * 4) / len;
      } else {
        this.originalPoint = null;
      }

      // Creates the preview shape (lazy)
      if (this.shape == null) {
        var dx = Math.abs(me.getGraphX() - this.first.x);
        var dy = Math.abs(me.getGraphY() - this.first.y);

        if (dx > this.graph.tolerance || dy > this.graph.tolerance) {
          this.shape = this.createShape();

          if (this.edgeState != null) {
            this.shape.apply(this.edgeState);
          }

          // Revalidates current connection
          this.updateCurrentState(me, point);
        }
      }

      // Updates the points in the preview edge
      if (this.shape != null) {
        if (this.edgeState != null) {
          this.shape.points = this.edgeState.absolutePoints;
        } else {
          var pts = [pt2];

          if (this.waypoints != null) {
            pts = pts.concat(this.waypoints);
          }

          pts.push(current);
          this.shape.points = pts;
        }

        this.drawPreview();
      }

      // Makes sure endpoint of edge is visible during connect
      if (this.cursor != null) {
        this.graph.container.style.cursor = this.cursor;
      }

      mxEvent.consume(me.getEvent());
      me.consume();
    } else if (!this.isEnabled() || !this.graph.isEnabled()) {
      this.constraintHandler.reset();
    } else if (this.previous != this.currentState && this.edgeState == null) {
      this.destroyIcons();

      // Sets the cursor on the current shape
      if (this.currentState != null && this.error == null && this.constraintHandler.currentConstraint == null) {
        this.icons = this.createIcons(this.currentState);

        if (this.icons == null) {
          this.currentState.setCursor(mxConstants.CURSOR_CONNECT);
          me.consume();
        }
      }

      this.previous = this.currentState;
    } else if (
      this.previous == this.currentState &&
      this.currentState != null &&
      this.icons == null &&
      !this.graph.isMouseDown
    ) {
      // Makes sure that no cursors are changed
      me.consume();
    }

    if (!this.graph.isMouseDown && this.currentState != null && this.icons != null) {
      var hitsIcon = false;
      var target = me.getSource();

      for (var i = 0; i < this.icons.length && !hitsIcon; i++) {
        hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node;
      }

      if (!hitsIcon) {
        this.updateIcons(this.currentState, this.icons, me);
      }
    }
  } else {
    this.constraintHandler.reset();
  }
};

/**
 * Function: updateEdgeState
 *
 * Updates <edgeState>.
 */
mxConnectionHandler.prototype.updateEdgeState = function (current, constraint) {
  // TODO: Use generic method for writing constraint to style
  if (this.sourceConstraint != null && this.sourceConstraint.point != null) {
    this.edgeState.style[mxConstants.STYLE_EXIT_X] = this.sourceConstraint.point.x;
    this.edgeState.style[mxConstants.STYLE_EXIT_Y] = this.sourceConstraint.point.y;
  }

  if (constraint != null && constraint.point != null) {
    this.edgeState.style[mxConstants.STYLE_ENTRY_X] = constraint.point.x;
    this.edgeState.style[mxConstants.STYLE_ENTRY_Y] = constraint.point.y;
  } else {
    delete this.edgeState.style[mxConstants.STYLE_ENTRY_X];
    delete this.edgeState.style[mxConstants.STYLE_ENTRY_Y];
  }

  this.edgeState.absolutePoints = [null, this.currentState != null ? null : current];
  this.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, true, this.sourceConstraint);

  if (this.currentState != null) {
    if (constraint == null) {
      constraint = this.graph.getConnectionConstraint(this.edgeState, this.previous, false);
    }

    this.edgeState.setAbsoluteTerminalPoint(null, false);
    this.graph.view.updateFixedTerminalPoint(this.edgeState, this.currentState, false, constraint);
  }

  // Scales and translates the waypoints to the model
  var realPoints = null;

  if (this.waypoints != null) {
    realPoints = [];

    for (var i = 0; i < this.waypoints.length; i++) {
      var pt = this.waypoints[i].clone();
      this.convertWaypoint(pt);
      realPoints[i] = pt;
    }
  }

  this.graph.view.updatePoints(this.edgeState, realPoints, this.previous, this.currentState);
  this.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState);
};

/**
 * Function: getTargetPerimeterPoint
 *
 * Returns the perimeter point for the given target state.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the target cell state.
 * me - <mxMouseEvent> that represents the mouse move.
 */
mxConnectionHandler.prototype.getTargetPerimeterPoint = function (state, me) {
  var result = null;
  var view = state.view;
  var targetPerimeter = view.getPerimeterFunction(state);

  if (targetPerimeter != null) {
    var next =
      this.waypoints != null && this.waypoints.length > 0
        ? this.waypoints[this.waypoints.length - 1]
        : new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
    var tmp = targetPerimeter(view.getPerimeterBounds(state), this.edgeState, next, false);

    if (tmp != null) {
      result = tmp;
    }
  } else {
    result = new mxPoint(state.getCenterX(), state.getCenterY());
  }

  return result;
};

/**
 * Function: getSourcePerimeterPoint
 *
 * Hook to update the icon position(s) based on a mouseOver event. This is
 * an empty implementation.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the target cell state.
 * next - <mxPoint> that represents the next point along the previewed edge.
 * me - <mxMouseEvent> that represents the mouse move.
 */
mxConnectionHandler.prototype.getSourcePerimeterPoint = function (state, next, me) {
  var result = null;
  var view = state.view;
  var sourcePerimeter = view.getPerimeterFunction(state);
  var c = new mxPoint(state.getCenterX(), state.getCenterY());

  if (sourcePerimeter != null) {
    var theta = mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0);
    var rad = -theta * (Math.PI / 180);

    if (theta != 0) {
      next = mxUtils.getRotatedPoint(new mxPoint(next.x, next.y), Math.cos(rad), Math.sin(rad), c);
    }

    var tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false);

    if (tmp != null) {
      if (theta != 0) {
        tmp = mxUtils.getRotatedPoint(new mxPoint(tmp.x, tmp.y), Math.cos(-rad), Math.sin(-rad), c);
      }

      result = tmp;
    }
  } else {
    result = c;
  }

  return result;
};

/**
 * Function: updateIcons
 *
 * Hook to update the icon position(s) based on a mouseOver event. This is
 * an empty implementation.
 *
 * Parameters:
 *
 * state - <mxCellState> under the mouse.
 * icons - Array of currently displayed icons.
 * me - <mxMouseEvent> that contains the mouse event.
 */
mxConnectionHandler.prototype.updateIcons = function (state, icons, me) {
  // empty
};

/**
 * Function: isStopEvent
 *
 * Returns true if the given mouse up event should stop this handler. The
 * connection will be created if <error> is null. Note that this is only
 * called if <waypointsEnabled> is true. This implemtation returns true
 * if there is a cell state in the given event.
 */
mxConnectionHandler.prototype.isStopEvent = function (me) {
  return me.getState() != null;
};

/**
 * Function: addWaypoint
 *
 * Adds the waypoint for the given event to <waypoints>.
 */
mxConnectionHandler.prototype.addWaypointForEvent = function (me) {
  var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
  var dx = Math.abs(point.x - this.first.x);
  var dy = Math.abs(point.y - this.first.y);
  var addPoint =
    this.waypoints != null || (this.mouseDownCounter > 1 && (dx > this.graph.tolerance || dy > this.graph.tolerance));

  if (addPoint) {
    if (this.waypoints == null) {
      this.waypoints = [];
    }

    var scale = this.graph.view.scale;
    var point = new mxPoint(
      this.graph.snap(me.getGraphX() / scale) * scale,
      this.graph.snap(me.getGraphY() / scale) * scale
    );
    this.waypoints.push(point);
  }
};

/**
 * Function: checkConstraints
 *
 * Returns true if the connection for the given constraints is valid. This
 * implementation returns true if the constraints are not pointing to the
 * same fixed connection point.
 */
mxConnectionHandler.prototype.checkConstraints = function (c1, c2) {
  return (
    c1 == null ||
    c2 == null ||
    c1.point == null ||
    c2.point == null ||
    !c1.point.equals(c2.point) ||
    c1.dx != c2.dx ||
    c1.dy != c2.dy ||
    c1.perimeter != c2.perimeter
  );
};

/**
 * Function: mouseUp
 *
 * Handles the event by inserting the new connection.
 */
mxConnectionHandler.prototype.mouseUp = function (sender, me) {
  if (!me.isConsumed() && this.isConnecting()) {
    if (this.waypointsEnabled && !this.isStopEvent(me)) {
      this.addWaypointForEvent(me);
      me.consume();

      return;
    }

    var c1 = this.sourceConstraint;
    var c2 = this.constraintHandler.currentConstraint;

    var source = this.previous != null ? this.previous.cell : null;
    var target = null;

    if (this.constraintHandler.currentConstraint != null && this.constraintHandler.currentFocus != null) {
      target = this.constraintHandler.currentFocus.cell;
    }

    if (target == null && this.currentState != null) {
      target = this.currentState.cell;
    }

    // Inserts the edge if no validation error exists and if constraints differ
    if (this.error == null && (source == null || target == null || source != target || this.checkConstraints(c1, c2))) {
      this.connect(source, target, me.getEvent(), me.getCell());
    } else {
      // Selects the source terminal for self-references
      if (
        this.previous != null &&
        this.marker.validState != null &&
        this.previous.cell == this.marker.validState.cell
      ) {
        this.graph.selectCellForEvent(this.marker.source, me.getEvent());
      }

      // Displays the error message if it is not an empty string,
      // for empty error messages, the event is silently dropped
      if (this.error != null && this.error.length > 0) {
        this.graph.validationAlert(this.error);
      }
    }

    // Redraws the connect icons and resets the handler state
    this.destroyIcons();
    me.consume();
  }

  if (this.first != null) {
    this.reset();
  }
};

/**
 * Function: reset
 *
 * Resets the state of this handler.
 */
mxConnectionHandler.prototype.reset = function () {
  if (this.shape != null) {
    this.shape.destroy();
    this.shape = null;
  }

  // Resets the cursor on the container
  if (this.cursor != null && this.graph.container != null) {
    this.graph.container.style.cursor = '';
  }

  this.destroyIcons();
  this.marker.reset();
  this.constraintHandler.reset();
  this.originalPoint = null;
  this.currentPoint = null;
  this.edgeState = null;
  this.previous = null;
  this.error = null;
  this.sourceConstraint = null;
  this.mouseDownCounter = 0;
  this.first = null;

  this.fireEvent(new mxEventObject(mxEvent.RESET));
};

/**
 * Function: drawPreview
 *
 * Redraws the preview edge using the color and width returned by
 * <getEdgeColor> and <getEdgeWidth>.
 */
mxConnectionHandler.prototype.drawPreview = function () {
  this.updatePreview(this.error == null);
  this.shape.redraw();
};

/**
 * Function: getEdgeColor
 *
 * Returns the color used to draw the preview edge. This returns green if
 * there is no edge validation error and red otherwise.
 *
 * Parameters:
 *
 * valid - Boolean indicating if the color for a valid edge should be
 * returned.
 */
mxConnectionHandler.prototype.updatePreview = function (valid) {
  this.shape.strokewidth = this.getEdgeWidth(valid);
  this.shape.stroke = this.getEdgeColor(valid);
};

/**
 * Function: getEdgeColor
 *
 * Returns the color used to draw the preview edge. This returns green if
 * there is no edge validation error and red otherwise.
 *
 * Parameters:
 *
 * valid - Boolean indicating if the color for a valid edge should be
 * returned.
 */
mxConnectionHandler.prototype.getEdgeColor = function (valid) {
  return valid ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR;
};

/**
 * Function: getEdgeWidth
 *
 * Returns the width used to draw the preview edge. This returns 3 if
 * there is no edge validation error and 1 otherwise.
 *
 * Parameters:
 *
 * valid - Boolean indicating if the width for a valid edge should be
 * returned.
 */
mxConnectionHandler.prototype.getEdgeWidth = function (valid) {
  return valid ? 3 : 1;
};

/**
 * Function: connect
 *
 * Connects the given source and target using a new edge. This
 * implementation uses <createEdge> to create the edge.
 *
 * Parameters:
 *
 * source - <mxCell> that represents the source terminal.
 * target - <mxCell> that represents the target terminal.
 * evt - Mousedown event of the connect gesture.
 * dropTarget - <mxCell> that represents the cell under the mouse when it was
 * released.
 */
mxConnectionHandler.prototype.connect = function (source, target, evt, dropTarget) {
  if (target != null || this.isCreateTarget(evt) || this.graph.allowDanglingEdges) {
    // Uses the common parent of source and target or
    // the default parent to insert the edge
    var model = this.graph.getModel();
    var terminalInserted = false;
    var edge = null;

    model.beginUpdate();
    try {
      if (source != null && target == null && !this.graph.isIgnoreTerminalEvent(evt) && this.isCreateTarget(evt)) {
        target = this.createTargetVertex(evt, source);

        if (target != null) {
          dropTarget = this.graph.getDropTarget([target], evt, dropTarget);
          terminalInserted = true;

          // Disables edges as drop targets if the target cell was created
          // FIXME: Should not shift if vertex was aligned (same in Java)
          if (dropTarget == null || !this.graph.getModel().isEdge(dropTarget)) {
            var pstate = this.graph.getView().getState(dropTarget);

            if (pstate != null) {
              var tmp = model.getGeometry(target);
              tmp.x -= pstate.origin.x;
              tmp.y -= pstate.origin.y;
            }
          } else {
            dropTarget = this.graph.getDefaultParent();
          }

          this.graph.addCell(target, dropTarget);
        }
      }

      var parent = this.graph.getDefaultParent();

      if (
        source != null &&
        target != null &&
        model.getParent(source) == model.getParent(target) &&
        model.getParent(model.getParent(source)) != model.getRoot()
      ) {
        parent = model.getParent(source);

        if (
          source.geometry != null &&
          source.geometry.relative &&
          target.geometry != null &&
          target.geometry.relative
        ) {
          parent = model.getParent(parent);
        }
      }

      // Uses the value of the preview edge state for inserting
      // the new edge into the graph
      var value = null;
      var style = null;

      if (this.edgeState != null) {
        value = this.edgeState.cell.value;
        style = this.edgeState.cell.style;
      }

      edge = this.insertEdge(parent, null, value, source, target, style);

      if (edge != null) {
        // Updates the connection constraints
        this.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint);
        this.graph.setConnectionConstraint(edge, target, false, this.constraintHandler.currentConstraint);

        // Uses geometry of the preview edge state
        if (this.edgeState != null) {
          model.setGeometry(edge, this.edgeState.cell.geometry);
        }

        var parent = model.getParent(source);

        // Inserts edge before source
        if (this.isInsertBefore(edge, source, target, evt, dropTarget)) {
          var index = null;
          var tmp = source;

          while (tmp.parent != null && tmp.geometry != null && tmp.geometry.relative && tmp.parent != edge.parent) {
            tmp = this.graph.model.getParent(tmp);
          }

          if (tmp != null && tmp.parent != null && tmp.parent == edge.parent) {
            model.add(parent, edge, tmp.parent.getIndex(tmp));
          }
        }

        // Makes sure the edge has a non-null, relative geometry
        var geo = model.getGeometry(edge);

        if (geo == null) {
          geo = new mxGeometry();
          geo.relative = true;

          model.setGeometry(edge, geo);
        }

        // Uses scaled waypoints in geometry
        if (this.waypoints != null && this.waypoints.length > 0) {
          var s = this.graph.view.scale;
          var tr = this.graph.view.translate;
          geo.points = [];

          for (var i = 0; i < this.waypoints.length; i++) {
            var pt = this.waypoints[i];
            geo.points.push(new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y));
          }
        }

        if (target == null) {
          var t = this.graph.view.translate;
          var s = this.graph.view.scale;
          var pt =
            this.originalPoint != null
              ? new mxPoint(this.originalPoint.x / s - t.x, this.originalPoint.y / s - t.y)
              : new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);
          pt.x -= this.graph.panDx / this.graph.view.scale;
          pt.y -= this.graph.panDy / this.graph.view.scale;
          geo.setTerminalPoint(pt, false);
        }

        this.fireEvent(
          new mxEventObject(
            mxEvent.CONNECT,
            'cell',
            edge,
            'terminal',
            target,
            'event',
            evt,
            'target',
            dropTarget,
            'terminalInserted',
            terminalInserted
          )
        );
      }
    } catch (e) {
      mxLog.show();
      mxLog.debug(e.message);
    } finally {
      model.endUpdate();
    }

    if (this.select) {
      this.selectCells(edge, terminalInserted ? target : null);
    }
  }
};

/**
 * Function: selectCells
 *
 * Selects the given edge after adding a new connection. The target argument
 * contains the target vertex if one has been inserted.
 */
mxConnectionHandler.prototype.selectCells = function (edge, target) {
  this.graph.setSelectionCell(edge);
};

/**
 * Function: insertEdge
 *
 * Creates, inserts and returns the new edge for the given parameters. This
 * implementation does only use <createEdge> if <factoryMethod> is defined,
 * otherwise <mxGraph.insertEdge> will be used.
 */
mxConnectionHandler.prototype.insertEdge = function (parent, id, value, source, target, style) {
  if (this.factoryMethod == null) {
    return this.graph.insertEdge(parent, id, value, source, target, style);
  } else {
    var edge = this.createEdge(value, source, target, style);
    edge = this.graph.addEdge(edge, parent, source, target);

    return edge;
  }
};

/**
 * Function: createTargetVertex
 *
 * Hook method for creating new vertices on the fly if no target was
 * under the mouse. This is only called if <createTarget> is true and
 * returns null.
 *
 * Parameters:
 *
 * evt - Mousedown event of the connect gesture.
 * source - <mxCell> that represents the source terminal.
 */
mxConnectionHandler.prototype.createTargetVertex = function (evt, source) {
  // Uses the first non-relative source
  var geo = this.graph.getCellGeometry(source);

  while (geo != null && geo.relative) {
    source = this.graph.getModel().getParent(source);
    geo = this.graph.getCellGeometry(source);
  }

  var clone = this.graph.cloneCell(source);
  var geo = this.graph.getModel().getGeometry(clone);

  if (geo != null) {
    var t = this.graph.view.translate;
    var s = this.graph.view.scale;
    var point = new mxPoint(this.currentPoint.x / s - t.x, this.currentPoint.y / s - t.y);
    geo.x = Math.round(point.x - geo.width / 2 - this.graph.panDx / s);
    geo.y = Math.round(point.y - geo.height / 2 - this.graph.panDy / s);

    // Aligns with source if within certain tolerance
    var tol = this.getAlignmentTolerance();

    if (tol > 0) {
      var sourceState = this.graph.view.getState(source);

      if (sourceState != null) {
        var x = sourceState.x / s - t.x;
        var y = sourceState.y / s - t.y;

        if (Math.abs(x - geo.x) <= tol) {
          geo.x = Math.round(x);
        }

        if (Math.abs(y - geo.y) <= tol) {
          geo.y = Math.round(y);
        }
      }
    }
  }

  return clone;
};

/**
 * Function: getAlignmentTolerance
 *
 * Returns the tolerance for aligning new targets to sources. This returns the grid size / 2.
 */
mxConnectionHandler.prototype.getAlignmentTolerance = function (evt) {
  return this.graph.isGridEnabled() ? this.graph.gridSize / 2 : this.graph.tolerance;
};

/**
 * Function: createEdge
 *
 * Creates and returns a new edge using <factoryMethod> if one exists. If
 * no factory method is defined, then a new default edge is returned. The
 * source and target arguments are informal, the actual connection is
 * setup later by the caller of this function.
 *
 * Parameters:
 *
 * value - Value to be used for creating the edge.
 * source - <mxCell> that represents the source terminal.
 * target - <mxCell> that represents the target terminal.
 * style - Optional style from the preview edge.
 */
mxConnectionHandler.prototype.createEdge = function (value, source, target, style) {
  var edge = null;

  // Creates a new edge using the factoryMethod
  if (this.factoryMethod != null) {
    edge = this.factoryMethod(source, target, style);
  }

  if (edge == null) {
    edge = new mxCell(value || '');
    edge.setEdge(true);
    edge.setStyle(style);

    var geo = new mxGeometry();
    geo.relative = true;
    edge.setGeometry(geo);
  }

  return edge;
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes. This should be
 * called on all instances. It is called automatically for the built-in
 * instance created for each <mxGraph>.
 */
mxConnectionHandler.prototype.destroy = function () {
  this.graph.removeMouseListener(this);

  if (this.shape != null) {
    this.shape.destroy();
    this.shape = null;
  }

  if (this.marker != null) {
    this.marker.destroy();
    this.marker = null;
  }

  if (this.constraintHandler != null) {
    this.constraintHandler.destroy();
    this.constraintHandler = null;
  }

  if (this.changeHandler != null) {
    this.graph.getModel().removeListener(this.changeHandler);
    this.graph.getView().removeListener(this.changeHandler);
    this.changeHandler = null;
  }

  if (this.drillHandler != null) {
    this.graph.removeListener(this.drillHandler);
    this.graph.getView().removeListener(this.drillHandler);
    this.drillHandler = null;
  }

  if (this.escapeHandler != null) {
    this.graph.removeListener(this.escapeHandler);
    this.escapeHandler = null;
  }
};

/**
 * handler/mxConstraintHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxConstraintHandler
 *
 * Handles constraints on connection targets. This class is in charge of
 * showing fixed points when the mouse is over a vertex and handles constraints
 * to establish new connections.
 *
 * Constructor: mxConstraintHandler
 *
 * Constructs an new constraint handler.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 * factoryMethod - Optional function to create the edge. The function takes
 * the source and target <mxCell> as the first and second argument and
 * returns the <mxCell> that represents the new edge.
 */
function mxConstraintHandler(graph) {
  this.graph = graph;

  // Adds a graph model listener to update the current focus on changes
  this.resetHandler = mxUtils.bind(this, function (sender, evt) {
    if (this.currentFocus != null && this.graph.view.getState(this.currentFocus.cell) == null) {
      this.reset();
    } else {
      this.redraw();
    }
  });

  this.graph.model.addListener(mxEvent.CHANGE, this.resetHandler);
  this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.resetHandler);
  this.graph.view.addListener(mxEvent.TRANSLATE, this.resetHandler);
  this.graph.view.addListener(mxEvent.SCALE, this.resetHandler);
  this.graph.addListener(mxEvent.ROOT, this.resetHandler);
}

/**
 * Variable: pointImage
 *
 * <mxImage> to be used as the image for fixed connection points.
 */
mxConstraintHandler.prototype.pointImage = new mxImage(mxClient.imageBasePath + '/point.gif', 5, 5);

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxConstraintHandler.prototype.graph = null;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxConstraintHandler.prototype.enabled = true;

/**
 * Variable: highlightColor
 *
 * Specifies the color for the highlight. Default is <mxConstants.DEFAULT_VALID_COLOR>.
 */
mxConstraintHandler.prototype.highlightColor = mxConstants.DEFAULT_VALID_COLOR;

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxConstraintHandler.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 *
 * Parameters:
 *
 * enabled - Boolean that specifies the new enabled state.
 */
mxConstraintHandler.prototype.setEnabled = function (enabled) {
  this.enabled = enabled;
};

/**
 * Function: reset
 *
 * Resets the state of this handler.
 */
mxConstraintHandler.prototype.reset = function () {
  if (this.focusIcons != null) {
    for (var i = 0; i < this.focusIcons.length; i++) {
      this.focusIcons[i].destroy();
    }

    this.focusIcons = null;
  }

  if (this.focusHighlight != null) {
    this.focusHighlight.destroy();
    this.focusHighlight = null;
  }

  this.currentConstraint = null;
  this.currentFocusArea = null;
  this.currentPoint = null;
  this.currentFocus = null;
  this.focusPoints = null;
};

/**
 * Function: getTolerance
 *
 * Returns the tolerance to be used for intersecting connection points. This
 * implementation returns <mxGraph.tolerance>.
 *
 * Parameters:
 *
 * me - <mxMouseEvent> whose tolerance should be returned.
 */
mxConstraintHandler.prototype.getTolerance = function (me) {
  return this.graph.getTolerance();
};

/**
 * Function: getImageForConstraint
 *
 * Returns the tolerance to be used for intersecting connection points.
 */
mxConstraintHandler.prototype.getImageForConstraint = function (state, constraint, point) {
  return this.pointImage;
};

/**
 * Function: isEventIgnored
 *
 * Returns true if the given <mxMouseEvent> should be ignored in <update>. This
 * implementation always returns false.
 */
mxConstraintHandler.prototype.isEventIgnored = function (me, source) {
  return false;
};

/**
 * Function: isStateIgnored
 *
 * Returns true if the given state should be ignored. This always returns false.
 */
mxConstraintHandler.prototype.isStateIgnored = function (state, source) {
  return false;
};

/**
 * Function: destroyIcons
 *
 * Destroys the <focusIcons> if they exist.
 */
mxConstraintHandler.prototype.destroyIcons = function () {
  if (this.focusIcons != null) {
    for (var i = 0; i < this.focusIcons.length; i++) {
      this.focusIcons[i].destroy();
    }

    this.focusIcons = null;
    this.focusPoints = null;
  }
};

/**
 * Function: destroyFocusHighlight
 *
 * Destroys the <focusHighlight> if one exists.
 */
mxConstraintHandler.prototype.destroyFocusHighlight = function () {
  if (this.focusHighlight != null) {
    this.focusHighlight.destroy();
    this.focusHighlight = null;
  }
};

/**
 * Function: isKeepFocusEvent
 *
 * Returns true if the current focused state should not be changed for the given event.
 * This returns true if shift and alt are pressed.
 */
mxConstraintHandler.prototype.isKeepFocusEvent = function (me) {
  return mxEvent.isShiftDown(me.getEvent());
};

/**
 * Function: getCellForEvent
 *
 * Returns the cell for the given event.
 */
mxConstraintHandler.prototype.getCellForEvent = function (me, point) {
  var cell = me.getCell();

  // Gets cell under actual point if different from event location
  if (cell == null && point != null && (me.getGraphX() != point.x || me.getGraphY() != point.y)) {
    cell = this.graph.getCellAt(point.x, point.y);
  }

  // Uses connectable parent vertex if one exists
  if (cell != null && !this.graph.isCellConnectable(cell)) {
    var parent = this.graph.getModel().getParent(cell);

    if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) {
      cell = parent;
    }
  }

  return this.graph.isCellLocked(cell) ? null : cell;
};

/**
 * Function: update
 *
 * Updates the state of this handler based on the given <mxMouseEvent>.
 * Source is a boolean indicating if the cell is a source or target.
 */
mxConstraintHandler.prototype.update = function (me, source, existingEdge, point) {
  if (this.isEnabled() && !this.isEventIgnored(me)) {
    // Lazy installation of mouseleave handler
    if (this.mouseleaveHandler == null && this.graph.container != null) {
      this.mouseleaveHandler = mxUtils.bind(this, function () {
        this.reset();
      });

      mxEvent.addListener(this.graph.container, 'mouseleave', this.resetHandler);
    }

    var tol = this.getTolerance(me);
    var x = point != null ? point.x : me.getGraphX();
    var y = point != null ? point.y : me.getGraphY();
    var grid = new mxRectangle(x - tol, y - tol, 2 * tol, 2 * tol);
    var mouse = new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol);
    var state = this.graph.view.getState(this.getCellForEvent(me, point));

    // Keeps focus icons visible while over vertex bounds and no other cell under mouse or shift is pressed
    if (
      !this.isKeepFocusEvent(me) &&
      (this.currentFocusArea == null ||
        this.currentFocus == null ||
        state != null ||
        !this.graph.getModel().isVertex(this.currentFocus.cell) ||
        !mxUtils.intersects(this.currentFocusArea, mouse)) &&
      state != this.currentFocus
    ) {
      this.currentFocusArea = null;
      this.currentFocus = null;
      this.setFocus(me, state, source);
    }

    this.currentConstraint = null;
    this.currentPoint = null;
    var minDistSq = null;

    if (this.focusIcons != null && this.constraints != null && (state == null || this.currentFocus == state)) {
      var cx = mouse.getCenterX();
      var cy = mouse.getCenterY();

      for (var i = 0; i < this.focusIcons.length; i++) {
        var dx = cx - this.focusIcons[i].bounds.getCenterX();
        var dy = cy - this.focusIcons[i].bounds.getCenterY();
        var tmp = dx * dx + dy * dy;

        if (
          (this.intersects(this.focusIcons[i], mouse, source, existingEdge) ||
            (point != null && this.intersects(this.focusIcons[i], grid, source, existingEdge))) &&
          (minDistSq == null || tmp < minDistSq)
        ) {
          this.currentConstraint = this.constraints[i];
          this.currentPoint = this.focusPoints[i];
          minDistSq = tmp;

          var tmp = this.focusIcons[i].bounds.clone();
          tmp.grow(mxConstants.HIGHLIGHT_SIZE + 1);
          tmp.width -= 1;
          tmp.height -= 1;

          if (this.focusHighlight == null) {
            var hl = this.createHighlightShape();
            hl.dialect =
              this.graph.dialect == mxConstants.DIALECT_SVG ? mxConstants.DIALECT_SVG : mxConstants.DIALECT_VML;
            hl.pointerEvents = false;

            hl.init(this.graph.getView().getOverlayPane());
            this.focusHighlight = hl;

            var getState = mxUtils.bind(this, function () {
              return this.currentFocus != null ? this.currentFocus : state;
            });

            mxEvent.redirectMouseEvents(hl.node, this.graph, getState);
          }

          this.focusHighlight.bounds = tmp;
          this.focusHighlight.redraw();
        }
      }
    }

    if (this.currentConstraint == null) {
      this.destroyFocusHighlight();
    }
  } else {
    this.currentConstraint = null;
    this.currentFocus = null;
    this.currentPoint = null;
  }
};

/**
 * Function: redraw
 *
 * Transfers the focus to the given state as a source or target terminal. If
 * the handler is not enabled then the outline is painted, but the constraints
 * are ignored.
 */
mxConstraintHandler.prototype.redraw = function () {
  if (this.currentFocus != null && this.constraints != null && this.focusIcons != null) {
    var state = this.graph.view.getState(this.currentFocus.cell);
    this.currentFocus = state;
    this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height);

    for (var i = 0; i < this.constraints.length; i++) {
      var cp = this.graph.getConnectionPoint(state, this.constraints[i]);
      var img = this.getImageForConstraint(state, this.constraints[i], cp);

      var bounds = new mxRectangle(
        Math.round(cp.x - img.width / 2),
        Math.round(cp.y - img.height / 2),
        img.width,
        img.height
      );
      this.focusIcons[i].bounds = bounds;
      this.focusIcons[i].redraw();
      this.currentFocusArea.add(this.focusIcons[i].bounds);
      this.focusPoints[i] = cp;
    }
  }
};

/**
 * Function: setFocus
 *
 * Transfers the focus to the given state as a source or target terminal. If
 * the handler is not enabled then the outline is painted, but the constraints
 * are ignored.
 */
mxConstraintHandler.prototype.setFocus = function (me, state, source) {
  this.constraints =
    state != null && !this.isStateIgnored(state, source) && this.graph.isCellConnectable(state.cell)
      ? this.isEnabled()
        ? this.graph.getAllConnectionConstraints(state, source) || []
        : []
      : null;

  // Only uses cells which have constraints
  if (this.constraints != null) {
    this.currentFocus = state;
    this.currentFocusArea = new mxRectangle(state.x, state.y, state.width, state.height);

    if (this.focusIcons != null) {
      for (var i = 0; i < this.focusIcons.length; i++) {
        this.focusIcons[i].destroy();
      }

      this.focusIcons = null;
      this.focusPoints = null;
    }

    this.focusPoints = [];
    this.focusIcons = [];

    for (var i = 0; i < this.constraints.length; i++) {
      var cp = this.graph.getConnectionPoint(state, this.constraints[i]);
      var img = this.getImageForConstraint(state, this.constraints[i], cp);

      var src = img.src;
      var bounds = new mxRectangle(
        Math.round(cp.x - img.width / 2),
        Math.round(cp.y - img.height / 2),
        img.width,
        img.height
      );
      var icon = new mxImageShape(bounds, src);
      icon.dialect =
        this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
      icon.preserveImageAspect = false;
      icon.init(this.graph.getView().getDecoratorPane());

      // Fixes lost event tracking for images in quirks / IE8 standards
      if (mxClient.IS_QUIRKS || document.documentMode == 8) {
        mxEvent.addListener(icon.node, 'dragstart', function (evt) {
          mxEvent.consume(evt);

          return false;
        });
      }

      // Move the icon behind all other overlays
      if (icon.node.previousSibling != null) {
        icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
      }

      var getState = mxUtils.bind(this, function () {
        return this.currentFocus != null ? this.currentFocus : state;
      });

      icon.redraw();

      mxEvent.redirectMouseEvents(icon.node, this.graph, getState);
      this.currentFocusArea.add(icon.bounds);
      this.focusIcons.push(icon);
      this.focusPoints.push(cp);
    }

    this.currentFocusArea.grow(this.getTolerance(me));
  } else {
    this.destroyIcons();
    this.destroyFocusHighlight();
  }
};

/**
 * Function: createHighlightShape
 *
 * Create the shape used to paint the highlight.
 *
 * Returns true if the given icon intersects the given point.
 */
mxConstraintHandler.prototype.createHighlightShape = function () {
  var hl = new mxRectangleShape(null, this.highlightColor, this.highlightColor, mxConstants.HIGHLIGHT_STROKEWIDTH);
  hl.opacity = mxConstants.HIGHLIGHT_OPACITY;

  return hl;
};

/**
 * Function: intersects
 *
 * Returns true if the given icon intersects the given rectangle.
 */
mxConstraintHandler.prototype.intersects = function (icon, mouse, source, existingEdge) {
  return mxUtils.intersects(icon.bounds, mouse);
};

/**
 * Function: destroy
 *
 * Destroy this handler.
 */
mxConstraintHandler.prototype.destroy = function () {
  this.reset();

  if (this.resetHandler != null) {
    this.graph.model.removeListener(this.resetHandler);
    this.graph.view.removeListener(this.resetHandler);
    this.graph.removeListener(this.resetHandler);
    this.resetHandler = null;
  }

  if (this.mouseleaveHandler != null && this.graph.container != null) {
    mxEvent.removeListener(this.graph.container, 'mouseleave', this.mouseleaveHandler);
    this.mouseleaveHandler = null;
  }
};

/**
 * handler/mxRubberband.js
 */
/**
 * Copyright (c) 2006-2016, JGraph Ltd
 * Copyright (c) 2006-2016, Gaudenz Alder
 */
/**
 * Class: mxRubberband
 *
 * Event handler that selects rectangular regions. This is not built-into
 * <mxGraph>. To enable rubberband selection in a graph, use the following code.
 *
 * Example:
 *
 * (code)
 * var rubberband = new mxRubberband(graph);
 * (end)
 *
 * Constructor: mxRubberband
 *
 * Constructs an event handler that selects rectangular regions in the graph
 * using rubberband selection.
 */
function mxRubberband(graph) {
  if (graph != null) {
    this.graph = graph;
    this.graph.addMouseListener(this);

    // Handles force rubberband event
    this.forceRubberbandHandler = mxUtils.bind(this, function (sender, evt) {
      var evtName = evt.getProperty('eventName');
      var me = evt.getProperty('event');

      if (evtName == mxEvent.MOUSE_DOWN && this.isForceRubberbandEvent(me)) {
        var offset = mxUtils.getOffset(this.graph.container);
        var origin = mxUtils.getScrollOrigin(this.graph.container);
        origin.x -= offset.x;
        origin.y -= offset.y;
        this.start(me.getX() + origin.x, me.getY() + origin.y);
        me.consume(false);
      }
    });

    this.graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.forceRubberbandHandler);

    // Repaints the marquee after autoscroll
    this.panHandler = mxUtils.bind(this, function () {
      this.repaint();
    });

    this.graph.addListener(mxEvent.PAN, this.panHandler);

    // Does not show menu if any touch gestures take place after the trigger
    this.gestureHandler = mxUtils.bind(this, function (sender, eo) {
      if (this.first != null) {
        this.reset();
      }
    });

    this.graph.addListener(mxEvent.GESTURE, this.gestureHandler);

    // Automatic deallocation of memory
    if (mxClient.IS_IE) {
      mxEvent.addListener(
        window,
        'unload',
        mxUtils.bind(this, function () {
          this.destroy();
        })
      );
    }
  }
}

/**
 * Variable: defaultOpacity
 *
 * Specifies the default opacity to be used for the rubberband div. Default
 * is 20.
 */
mxRubberband.prototype.defaultOpacity = 20;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxRubberband.prototype.enabled = true;

/**
 * Variable: div
 *
 * Holds the DIV element which is currently visible.
 */
mxRubberband.prototype.div = null;

/**
 * Variable: sharedDiv
 *
 * Holds the DIV element which is used to display the rubberband.
 */
mxRubberband.prototype.sharedDiv = null;

/**
 * Variable: currentX
 *
 * Holds the value of the x argument in the last call to <update>.
 */
mxRubberband.prototype.currentX = 0;

/**
 * Variable: currentY
 *
 * Holds the value of the y argument in the last call to <update>.
 */
mxRubberband.prototype.currentY = 0;

/**
 * Variable: fadeOut
 *
 * Optional fade out effect. Default is false.
 */
mxRubberband.prototype.fadeOut = false;

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation returns
 * <enabled>.
 */
mxRubberband.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation updates
 * <enabled>.
 */
mxRubberband.prototype.setEnabled = function (enabled) {
  this.enabled = enabled;
};

/**
 * Function: isForceRubberbandEvent
 *
 * Returns true if the given <mxMouseEvent> should start rubberband selection.
 * This implementation returns true if the alt key is pressed.
 */
mxRubberband.prototype.isForceRubberbandEvent = function (me) {
  return mxEvent.isAltDown(me.getEvent());
};

/**
 * Function: mouseDown
 *
 * Handles the event by initiating a rubberband selection. By consuming the
 * event all subsequent events of the gesture are redirected to this
 * handler.
 */
mxRubberband.prototype.mouseDown = function (sender, me) {
  if (
    !me.isConsumed() &&
    this.isEnabled() &&
    this.graph.isEnabled() &&
    me.getState() == null &&
    !mxEvent.isMultiTouchEvent(me.getEvent())
  ) {
    var offset = mxUtils.getOffset(this.graph.container);
    var origin = mxUtils.getScrollOrigin(this.graph.container);
    origin.x -= offset.x;
    origin.y -= offset.y;
    this.start(me.getX() + origin.x, me.getY() + origin.y);

    // Does not prevent the default for this event so that the
    // event processing chain is still executed even if we start
    // rubberbanding. This is required eg. in ExtJs to hide the
    // current context menu. In mouseMove we'll make sure we're
    // not selecting anything while we're rubberbanding.
    me.consume(false);
  }
};

/**
 * Function: start
 *
 * Sets the start point for the rubberband selection.
 */
mxRubberband.prototype.start = function (x, y) {
  this.first = new mxPoint(x, y);

  var container = this.graph.container;

  function createMouseEvent(evt) {
    var me = new mxMouseEvent(evt);
    var pt = mxUtils.convertPoint(container, me.getX(), me.getY());

    me.graphX = pt.x;
    me.graphY = pt.y;

    return me;
  }

  this.dragHandler = mxUtils.bind(this, function (evt) {
    this.mouseMove(this.graph, createMouseEvent(evt));
  });

  this.dropHandler = mxUtils.bind(this, function (evt) {
    this.mouseUp(this.graph, createMouseEvent(evt));
  });

  // Workaround for rubberband stopping if the mouse leaves the container in Firefox
  if (mxClient.IS_FF) {
    mxEvent.addGestureListeners(document, null, this.dragHandler, this.dropHandler);
  }
};

/**
 * Function: mouseMove
 *
 * Handles the event by updating therubberband selection.
 */
mxRubberband.prototype.mouseMove = function (sender, me) {
  if (!me.isConsumed() && this.first != null) {
    var origin = mxUtils.getScrollOrigin(this.graph.container);
    var offset = mxUtils.getOffset(this.graph.container);
    origin.x -= offset.x;
    origin.y -= offset.y;
    var x = me.getX() + origin.x;
    var y = me.getY() + origin.y;
    var dx = this.first.x - x;
    var dy = this.first.y - y;
    var tol = this.graph.tolerance;

    if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol) {
      if (this.div == null) {
        this.div = this.createShape();
      }

      // Clears selection while rubberbanding. This is required because
      // the event is not consumed in mouseDown.
      mxUtils.clearSelection();

      this.update(x, y);
      me.consume();
    }
  }
};

/**
 * Function: createShape
 *
 * Creates the rubberband selection shape.
 */
mxRubberband.prototype.createShape = function () {
  if (this.sharedDiv == null) {
    this.sharedDiv = document.createElement('div');
    this.sharedDiv.className = 'mxRubberband';
    mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity);
  }

  this.graph.container.appendChild(this.sharedDiv);
  var result = this.sharedDiv;

  if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut) {
    this.sharedDiv = null;
  }

  return result;
};

/**
 * Function: isActive
 *
 * Returns true if this handler is active.
 */
mxRubberband.prototype.isActive = function (sender, me) {
  return this.div != null && this.div.style.display != 'none';
};

/**
 * Function: mouseUp
 *
 * Handles the event by selecting the region of the rubberband using
 * <mxGraph.selectRegion>.
 */
mxRubberband.prototype.mouseUp = function (sender, me) {
  var active = this.isActive();
  this.reset();

  if (active) {
    this.execute(me.getEvent());
    me.consume();
  }
};

/**
 * Function: execute
 *
 * Resets the state of this handler and selects the current region
 * for the given event.
 */
mxRubberband.prototype.execute = function (evt) {
  var rect = new mxRectangle(this.x, this.y, this.width, this.height);
  this.graph.selectRegion(rect, evt);
};

/**
 * Function: reset
 *
 * Resets the state of the rubberband selection.
 */
mxRubberband.prototype.reset = function () {
  if (this.div != null) {
    if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10) && this.fadeOut) {
      var temp = this.div;
      mxUtils.setPrefixedStyle(temp.style, 'transition', 'all 0.2s linear');
      temp.style.pointerEvents = 'none';
      temp.style.opacity = 0;

      window.setTimeout(function () {
        temp.parentNode.removeChild(temp);
      }, 200);
    } else {
      this.div.parentNode.removeChild(this.div);
    }
  }

  mxEvent.removeGestureListeners(document, null, this.dragHandler, this.dropHandler);
  this.dragHandler = null;
  this.dropHandler = null;

  this.currentX = 0;
  this.currentY = 0;
  this.first = null;
  this.div = null;
};

/**
 * Function: update
 *
 * Sets <currentX> and <currentY> and calls <repaint>.
 */
mxRubberband.prototype.update = function (x, y) {
  this.currentX = x;
  this.currentY = y;

  this.repaint();
};

/**
 * Function: repaint
 *
 * Computes the bounding box and updates the style of the <div>.
 */
mxRubberband.prototype.repaint = function () {
  if (this.div != null) {
    var x = this.currentX - this.graph.panDx;
    var y = this.currentY - this.graph.panDy;

    this.x = Math.min(this.first.x, x);
    this.y = Math.min(this.first.y, y);
    this.width = Math.max(this.first.x, x) - this.x;
    this.height = Math.max(this.first.y, y) - this.y;

    var dx = mxClient.IS_VML ? this.graph.panDx : 0;
    var dy = mxClient.IS_VML ? this.graph.panDy : 0;

    this.div.style.left = this.x + dx + 'px';
    this.div.style.top = this.y + dy + 'px';
    this.div.style.width = Math.max(1, this.width) + 'px';
    this.div.style.height = Math.max(1, this.height) + 'px';
  }
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes. This does
 * normally not need to be called, it is called automatically when the
 * window unloads.
 */
mxRubberband.prototype.destroy = function () {
  if (!this.destroyed) {
    this.destroyed = true;
    this.graph.removeMouseListener(this);
    this.graph.removeListener(this.forceRubberbandHandler);
    this.graph.removeListener(this.panHandler);
    this.reset();

    if (this.sharedDiv != null) {
      this.sharedDiv = null;
    }
  }
};

/**
 * handler/mxHandle.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxHandle
 *
 * Implements a single custom handle for vertices.
 *
 * Constructor: mxHandle
 *
 * Constructs a new handle for the given state.
 *
 * Parameters:
 *
 * state - <mxCellState> of the cell to be handled.
 */
function mxHandle(state, cursor, image, shape) {
  this.graph = state.view.graph;
  this.state = state;
  this.cursor = cursor != null ? cursor : this.cursor;
  this.image = image != null ? image : this.image;
  this.shape = shape != null ? shape : null;
  this.init();
}

/**
 * Variable: cursor
 *
 * Specifies the cursor to be used for this handle. Default is 'default'.
 */
mxHandle.prototype.cursor = 'default';

/**
 * Variable: image
 *
 * Specifies the <mxImage> to be used to render the handle. Default is null.
 */
mxHandle.prototype.image = null;

/**
 * Variable: ignoreGrid
 *
 * Default is false.
 */
mxHandle.prototype.ignoreGrid = false;

/**
 * Function: getPosition
 *
 * Hook for subclassers to return the current position of the handle.
 */
mxHandle.prototype.getPosition = function (bounds) {};

/**
 * Function: setPosition
 *
 * Hooks for subclassers to update the style in the <state>.
 */
mxHandle.prototype.setPosition = function (bounds, pt, me) {};

/**
 * Function: execute
 *
 * Hook for subclassers to execute the handle.
 */
mxHandle.prototype.execute = function (me) {};

/**
 * Function: copyStyle
 *
 * Sets the cell style with the given name to the corresponding value in <state>.
 */
mxHandle.prototype.copyStyle = function (key) {
  this.graph.setCellStyles(key, this.state.style[key], [this.state.cell]);
};

/**
 * Function: processEvent
 *
 * Processes the given <mxMouseEvent> and invokes <setPosition>.
 */
mxHandle.prototype.processEvent = function (me) {
  var scale = this.graph.view.scale;
  var tr = this.graph.view.translate;
  var pt = new mxPoint(me.getGraphX() / scale - tr.x, me.getGraphY() / scale - tr.y);

  // Center shape on mouse cursor
  if (this.shape != null && this.shape.bounds != null) {
    pt.x -= this.shape.bounds.width / scale / 4;
    pt.y -= this.shape.bounds.height / scale / 4;
  }

  // Snaps to grid for the rotated position then applies the rotation for the direction after that
  var alpha1 = -mxUtils.toRadians(this.getRotation());
  var alpha2 = -mxUtils.toRadians(this.getTotalRotation()) - alpha1;
  pt = this.flipPoint(
    this.rotatePoint(
      this.snapPoint(this.rotatePoint(pt, alpha1), this.ignoreGrid || !this.graph.isGridEnabledEvent(me.getEvent())),
      alpha2
    )
  );
  this.setPosition(this.state.getPaintBounds(), pt, me);
  this.redraw();
};

/**
 * Function: positionChanged
 *
 * Should be called after <setPosition> in <processEvent>.
 * This repaints the state using <mxCellRenderer>.
 */
mxHandle.prototype.positionChanged = function () {
  if (this.state.text != null) {
    this.state.text.apply(this.state);
  }

  if (this.state.shape != null) {
    this.state.shape.apply(this.state);
  }

  this.graph.cellRenderer.redraw(this.state, true);
};

/**
 * Function: getRotation
 *
 * Returns the rotation defined in the style of the cell.
 */
mxHandle.prototype.getRotation = function () {
  if (this.state.shape != null) {
    return this.state.shape.getRotation();
  }

  return 0;
};

/**
 * Function: getTotalRotation
 *
 * Returns the rotation from the style and the rotation from the direction of
 * the cell.
 */
mxHandle.prototype.getTotalRotation = function () {
  if (this.state.shape != null) {
    return this.state.shape.getShapeRotation();
  }

  return 0;
};

/**
 * Function: init
 *
 * Creates and initializes the shapes required for this handle.
 */
mxHandle.prototype.init = function () {
  var html = this.isHtmlRequired();

  if (this.image != null) {
    this.shape = new mxImageShape(new mxRectangle(0, 0, this.image.width, this.image.height), this.image.src);
    this.shape.preserveImageAspect = false;
  } else if (this.shape == null) {
    this.shape = this.createShape(html);
  }

  this.initShape(html);
};

/**
 * Function: createShape
 *
 * Creates and returns the shape for this handle.
 */
mxHandle.prototype.createShape = function (html) {
  var bounds = new mxRectangle(0, 0, mxConstants.HANDLE_SIZE, mxConstants.HANDLE_SIZE);

  return new mxRectangleShape(bounds, mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
};

/**
 * Function: initShape
 *
 * Initializes <shape> and sets its cursor.
 */
mxHandle.prototype.initShape = function (html) {
  if (html && this.shape.isHtmlAllowed()) {
    this.shape.dialect = mxConstants.DIALECT_STRICTHTML;
    this.shape.init(this.graph.container);
  } else {
    this.shape.dialect =
      this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;

    if (this.cursor != null) {
      this.shape.init(this.graph.getView().getOverlayPane());
    }
  }

  mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);
  this.shape.node.style.cursor = this.cursor;
};

/**
 * Function: redraw
 *
 * Renders the shape for this handle.
 */
mxHandle.prototype.redraw = function () {
  if (this.shape != null && this.state.shape != null) {
    var pt = this.getPosition(this.state.getPaintBounds());

    if (pt != null) {
      var alpha = mxUtils.toRadians(this.getTotalRotation());
      pt = this.rotatePoint(this.flipPoint(pt), alpha);

      var scale = this.graph.view.scale;
      var tr = this.graph.view.translate;
      this.shape.bounds.x = Math.floor((pt.x + tr.x) * scale - this.shape.bounds.width / 2);
      this.shape.bounds.y = Math.floor((pt.y + tr.y) * scale - this.shape.bounds.height / 2);

      // Needed to force update of text bounds
      this.shape.redraw();
    }
  }
};

/**
 * Function: isHtmlRequired
 *
 * Returns true if this handle should be rendered in HTML. This returns true if
 * the text node is in the graph container.
 */
mxHandle.prototype.isHtmlRequired = function () {
  return this.state.text != null && this.state.text.node.parentNode == this.graph.container;
};

/**
 * Function: rotatePoint
 *
 * Rotates the point by the given angle.
 */
mxHandle.prototype.rotatePoint = function (pt, alpha) {
  var bounds = this.state.getCellBounds();
  var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
  var cos = Math.cos(alpha);
  var sin = Math.sin(alpha);

  return mxUtils.getRotatedPoint(pt, cos, sin, cx);
};

/**
 * Function: flipPoint
 *
 * Flips the given point vertically and/or horizontally.
 */
mxHandle.prototype.flipPoint = function (pt) {
  if (this.state.shape != null) {
    var bounds = this.state.getCellBounds();

    if (this.state.shape.flipH) {
      pt.x = 2 * bounds.x + bounds.width - pt.x;
    }

    if (this.state.shape.flipV) {
      pt.y = 2 * bounds.y + bounds.height - pt.y;
    }
  }

  return pt;
};

/**
 * Function: snapPoint
 *
 * Snaps the given point to the grid if ignore is false. This modifies
 * the given point in-place and also returns it.
 */
mxHandle.prototype.snapPoint = function (pt, ignore) {
  if (!ignore) {
    pt.x = this.graph.snap(pt.x);
    pt.y = this.graph.snap(pt.y);
  }

  return pt;
};

/**
 * Function: setVisible
 *
 * Shows or hides this handle.
 */
mxHandle.prototype.setVisible = function (visible) {
  if (this.shape != null && this.shape.node != null) {
    this.shape.node.style.display = visible ? '' : 'none';
  }
};

/**
 * Function: reset
 *
 * Resets the state of this handle by setting its visibility to true.
 */
mxHandle.prototype.reset = function () {
  this.setVisible(true);
  this.state.style = this.graph.getCellStyle(this.state.cell);
  this.positionChanged();
};

/**
 * Function: destroy
 *
 * Destroys this handle.
 */
mxHandle.prototype.destroy = function () {
  if (this.shape != null) {
    this.shape.destroy();
    this.shape = null;
  }
};

/**
 * handler/mxVertexHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxVertexHandler
 *
 * Event handler for resizing cells. This handler is automatically created in
 * <mxGraph.createHandler>.
 *
 * Constructor: mxVertexHandler
 *
 * Constructs an event handler that allows to resize vertices
 * and groups.
 *
 * Parameters:
 *
 * state - <mxCellState> of the cell to be resized.
 */
function mxVertexHandler(state) {
  if (state != null) {
    this.state = state;
    this.init();

    // Handles escape keystrokes
    this.escapeHandler = mxUtils.bind(this, function (sender, evt) {
      if (this.livePreview && this.index != null) {
        // Redraws the live preview
        this.state.view.graph.cellRenderer.redraw(this.state, true);

        // Redraws connected edges
        this.state.view.invalidate(this.state.cell);
        this.state.invalid = false;
        this.state.view.validate();
      }

      this.reset();
    });

    this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
  }
}

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxVertexHandler.prototype.graph = null;

/**
 * Variable: state
 *
 * Reference to the <mxCellState> being modified.
 */
mxVertexHandler.prototype.state = null;

/**
 * Variable: singleSizer
 *
 * Specifies if only one sizer handle at the bottom, right corner should be
 * used. Default is false.
 */
mxVertexHandler.prototype.singleSizer = false;

/**
 * Variable: index
 *
 * Holds the index of the current handle.
 */
mxVertexHandler.prototype.index = null;

/**
 * Variable: allowHandleBoundsCheck
 *
 * Specifies if the bounds of handles should be used for hit-detection in IE or
 * if <tolerance> > 0. Default is true.
 */
mxVertexHandler.prototype.allowHandleBoundsCheck = true;

/**
 * Variable: handleImage
 *
 * Optional <mxImage> to be used as handles. Default is null.
 */
mxVertexHandler.prototype.handleImage = null;

/**
 * Variable: handlesVisible
 *
 * If handles are currently visible.
 */
mxVertexHandler.prototype.handlesVisible = true;

/**
 * Variable: tolerance
 *
 * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
 */
mxVertexHandler.prototype.tolerance = 0;

/**
 * Variable: rotationEnabled
 *
 * Specifies if a rotation handle should be visible. Default is false.
 */
mxVertexHandler.prototype.rotationEnabled = false;

/**
 * Variable: parentHighlightEnabled
 *
 * Specifies if the parent should be highlighted if a child cell is selected.
 * Default is false.
 */
mxVertexHandler.prototype.parentHighlightEnabled = false;

/**
 * Variable: rotationRaster
 *
 * Specifies if rotation steps should be "rasterized" depening on the distance
 * to the handle. Default is true.
 */
mxVertexHandler.prototype.rotationRaster = true;

/**
 * Variable: rotationCursor
 *
 * Specifies the cursor for the rotation handle. Default is 'crosshair'.
 */
mxVertexHandler.prototype.rotationCursor = 'crosshair';

/**
 * Variable: livePreview
 *
 * Specifies if resize should change the cell in-place. This is an experimental
 * feature for non-touch devices. Default is false.
 */
mxVertexHandler.prototype.livePreview = false;

/**
 * Variable: movePreviewToFront
 *
 * Specifies if the live preview should be moved to the front.
 */
mxVertexHandler.prototype.movePreviewToFront = false;

/**
 * Variable: manageSizers
 *
 * Specifies if sizers should be hidden and spaced if the vertex is small.
 * Default is false.
 */
mxVertexHandler.prototype.manageSizers = false;

/**
 * Variable: constrainGroupByChildren
 *
 * Specifies if the size of groups should be constrained by the children.
 * Default is false.
 */
mxVertexHandler.prototype.constrainGroupByChildren = false;

/**
 * Variable: rotationHandleVSpacing
 *
 * Vertical spacing for rotation icon. Default is -16.
 */
mxVertexHandler.prototype.rotationHandleVSpacing = -16;

/**
 * Variable: horizontalOffset
 *
 * The horizontal offset for the handles. This is updated in <redrawHandles>
 * if <manageSizers> is true and the sizers are offset horizontally.
 */
mxVertexHandler.prototype.horizontalOffset = 0;

/**
 * Variable: verticalOffset
 *
 * The horizontal offset for the handles. This is updated in <redrawHandles>
 * if <manageSizers> is true and the sizers are offset vertically.
 */
mxVertexHandler.prototype.verticalOffset = 0;

/**
 * Function: init
 *
 * Initializes the shapes required for this vertex handler.
 */
mxVertexHandler.prototype.init = function () {
  this.graph = this.state.view.graph;
  this.selectionBounds = this.getSelectionBounds(this.state);
  this.bounds = new mxRectangle(
    this.selectionBounds.x,
    this.selectionBounds.y,
    this.selectionBounds.width,
    this.selectionBounds.height
  );
  this.selectionBorder = this.createSelectionShape(this.bounds);
  // VML dialect required here for event transparency in IE
  this.selectionBorder.dialect =
    this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
  this.selectionBorder.pointerEvents = false;
  this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
  this.selectionBorder.init(this.graph.getView().getOverlayPane());
  mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state);

  if (this.graph.isCellMovable(this.state.cell)) {
    this.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX);
  }

  // Adds the sizer handles
  if (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells) {
    var resizable = this.graph.isCellResizable(this.state.cell);
    this.sizers = [];

    if (resizable || (this.graph.isLabelMovable(this.state.cell) && this.state.width >= 2 && this.state.height >= 2)) {
      var i = 0;

      if (resizable) {
        if (!this.singleSizer) {
          this.sizers.push(this.createSizer('nw-resize', i++));
          this.sizers.push(this.createSizer('n-resize', i++));
          this.sizers.push(this.createSizer('ne-resize', i++));
          this.sizers.push(this.createSizer('w-resize', i++));
          this.sizers.push(this.createSizer('e-resize', i++));
          this.sizers.push(this.createSizer('sw-resize', i++));
          this.sizers.push(this.createSizer('s-resize', i++));
        }

        this.sizers.push(this.createSizer('se-resize', i++));
      }

      var geo = this.graph.model.getGeometry(this.state.cell);

      if (
        geo != null &&
        !geo.relative &&
        !this.graph.isSwimlane(this.state.cell) &&
        this.graph.isLabelMovable(this.state.cell)
      ) {
        // Marks this as the label handle for getHandleForEvent
        this.labelShape = this.createSizer(
          mxConstants.CURSOR_LABEL_HANDLE,
          mxEvent.LABEL_HANDLE,
          mxConstants.LABEL_HANDLE_SIZE,
          mxConstants.LABEL_HANDLE_FILLCOLOR
        );
        this.sizers.push(this.labelShape);
      }
    } else if (
      this.graph.isCellMovable(this.state.cell) &&
      !this.graph.isCellResizable(this.state.cell) &&
      this.state.width < 2 &&
      this.state.height < 2
    ) {
      this.labelShape = this.createSizer(
        mxConstants.CURSOR_MOVABLE_VERTEX,
        mxEvent.LABEL_HANDLE,
        null,
        mxConstants.LABEL_HANDLE_FILLCOLOR
      );
      this.sizers.push(this.labelShape);
    }
  }

  // Adds the rotation handler
  if (this.isRotationHandleVisible()) {
    this.rotationShape = this.createSizer(
      this.rotationCursor,
      mxEvent.ROTATION_HANDLE,
      mxConstants.HANDLE_SIZE + 3,
      mxConstants.HANDLE_FILLCOLOR
    );
    this.sizers.push(this.rotationShape);
  }

  this.customHandles = this.createCustomHandles();
  this.redraw();

  if (this.constrainGroupByChildren) {
    this.updateMinBounds();
  }
};

/**
 * Function: isRotationHandleVisible
 *
 * Returns true if the rotation handle should be showing.
 */
mxVertexHandler.prototype.isRotationHandleVisible = function () {
  return (
    this.graph.isEnabled() &&
    this.rotationEnabled &&
    this.graph.isCellRotatable(this.state.cell) &&
    (mxGraphHandler.prototype.maxCells <= 0 || this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells)
  );
};

/**
 * Function: isConstrainedEvent
 *
 * Returns true if the aspect ratio if the cell should be maintained.
 */
mxVertexHandler.prototype.isConstrainedEvent = function (me) {
  return mxEvent.isShiftDown(me.getEvent()) || this.state.style[mxConstants.STYLE_ASPECT] == 'fixed';
};

/**
 * Function: isCenteredEvent
 *
 * Returns true if the center of the vertex should be maintained during the resize.
 */
mxVertexHandler.prototype.isCenteredEvent = function (state, me) {
  return false;
};

/**
 * Function: createCustomHandles
 *
 * Returns an array of custom handles. This implementation returns null.
 */
mxVertexHandler.prototype.createCustomHandles = function () {
  return null;
};

/**
 * Function: updateMinBounds
 *
 * Initializes the shapes required for this vertex handler.
 */
mxVertexHandler.prototype.updateMinBounds = function () {
  var children = this.graph.getChildCells(this.state.cell);

  if (children.length > 0) {
    this.minBounds = this.graph.view.getBounds(children);

    if (this.minBounds != null) {
      var s = this.state.view.scale;
      var t = this.state.view.translate;

      this.minBounds.x -= this.state.x;
      this.minBounds.y -= this.state.y;
      this.minBounds.x /= s;
      this.minBounds.y /= s;
      this.minBounds.width /= s;
      this.minBounds.height /= s;
      this.x0 = this.state.x / s - t.x;
      this.y0 = this.state.y / s - t.y;
    }
  }
};

/**
 * Function: getSelectionBounds
 *
 * Returns the mxRectangle that defines the bounds of the selection
 * border.
 */
mxVertexHandler.prototype.getSelectionBounds = function (state) {
  return new mxRectangle(Math.round(state.x), Math.round(state.y), Math.round(state.width), Math.round(state.height));
};

/**
 * Function: createParentHighlightShape
 *
 * Creates the shape used to draw the selection border.
 */
mxVertexHandler.prototype.createParentHighlightShape = function (bounds) {
  return this.createSelectionShape(bounds);
};

/**
 * Function: createSelectionShape
 *
 * Creates the shape used to draw the selection border.
 */
mxVertexHandler.prototype.createSelectionShape = function (bounds) {
  var shape = new mxRectangleShape(mxRectangle.fromRectangle(bounds), null, this.getSelectionColor());
  shape.strokewidth = this.getSelectionStrokeWidth();
  shape.isDashed = this.isSelectionDashed();

  return shape;
};

/**
 * Function: getSelectionColor
 *
 * Returns <mxConstants.VERTEX_SELECTION_COLOR>.
 */
mxVertexHandler.prototype.getSelectionColor = function () {
  return mxConstants.VERTEX_SELECTION_COLOR;
};

/**
 * Function: getSelectionStrokeWidth
 *
 * Returns <mxConstants.VERTEX_SELECTION_STROKEWIDTH>.
 */
mxVertexHandler.prototype.getSelectionStrokeWidth = function () {
  return mxConstants.VERTEX_SELECTION_STROKEWIDTH;
};

/**
 * Function: isSelectionDashed
 *
 * Returns <mxConstants.VERTEX_SELECTION_DASHED>.
 */
mxVertexHandler.prototype.isSelectionDashed = function () {
  return mxConstants.VERTEX_SELECTION_DASHED;
};

/**
 * Function: createSizer
 *
 * Creates a sizer handle for the specified cursor and index and returns
 * the new <mxRectangleShape> that represents the handle.
 */
mxVertexHandler.prototype.createSizer = function (cursor, index, size, fillColor) {
  size = size || mxConstants.HANDLE_SIZE;

  var bounds = new mxRectangle(0, 0, size, size);
  var sizer = this.createSizerShape(bounds, index, fillColor);

  if (sizer.isHtmlAllowed() && this.state.text != null && this.state.text.node.parentNode == this.graph.container) {
    sizer.bounds.height -= 1;
    sizer.bounds.width -= 1;
    sizer.dialect = mxConstants.DIALECT_STRICTHTML;
    sizer.init(this.graph.container);
  } else {
    sizer.dialect =
      this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
    sizer.init(this.graph.getView().getOverlayPane());
  }

  mxEvent.redirectMouseEvents(sizer.node, this.graph, this.state);

  if (this.graph.isEnabled()) {
    sizer.setCursor(cursor);
  }

  if (!this.isSizerVisible(index)) {
    sizer.visible = false;
  }

  return sizer;
};

/**
 * Function: isSizerVisible
 *
 * Returns true if the sizer for the given index is visible.
 * This returns true for all given indices.
 */
mxVertexHandler.prototype.isSizerVisible = function (index) {
  return true;
};

/**
 * Function: createSizerShape
 *
 * Creates the shape used for the sizer handle for the specified bounds an
 * index. Only images and rectangles should be returned if support for HTML
 * labels with not foreign objects is required.
 */
mxVertexHandler.prototype.createSizerShape = function (bounds, index, fillColor) {
  if (this.handleImage != null) {
    bounds = new mxRectangle(bounds.x, bounds.y, this.handleImage.width, this.handleImage.height);
    var shape = new mxImageShape(bounds, this.handleImage.src);

    // Allows HTML rendering of the images
    shape.preserveImageAspect = false;

    return shape;
  } else if (index == mxEvent.ROTATION_HANDLE) {
    return new mxEllipse(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
  } else {
    return new mxRectangleShape(bounds, fillColor || mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
  }
};

/**
 * Function: createBounds
 *
 * Helper method to create an <mxRectangle> around the given centerpoint
 * with a width and height of 2*s or 6, if no s is given.
 */
mxVertexHandler.prototype.moveSizerTo = function (shape, x, y) {
  if (shape != null) {
    shape.bounds.x = Math.floor(x - shape.bounds.width / 2);
    shape.bounds.y = Math.floor(y - shape.bounds.height / 2);

    // Fixes visible inactive handles in VML
    if (shape.node != null && shape.node.style.display != 'none') {
      shape.redraw();
    }
  }
};

/**
 * Function: getHandleForEvent
 *
 * Returns the index of the handle for the given event. This returns the index
 * of the sizer from where the event originated or <mxEvent.LABEL_INDEX>.
 */
mxVertexHandler.prototype.getHandleForEvent = function (me) {
  // Connection highlight may consume events before they reach sizer handle
  var tol = !mxEvent.isMouseEvent(me.getEvent()) ? this.tolerance : 1;
  var hit =
    this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)
      ? new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol)
      : null;

  var checkShape = mxUtils.bind(this, function (shape) {
    var st =
      shape != null && shape.constructor != mxImageShape && this.allowHandleBoundsCheck
        ? shape.strokewidth + shape.svgStrokeTolerance
        : null;
    var real =
      st != null
        ? new mxRectangle(me.getGraphX() - Math.floor(st / 2), me.getGraphY() - Math.floor(st / 2), st, st)
        : hit;

    return (
      shape != null &&
      (me.isSource(shape) ||
        (real != null &&
          mxUtils.intersects(shape.bounds, real) &&
          shape.node.style.display != 'none' &&
          shape.node.style.visibility != 'hidden'))
    );
  });

  if (checkShape(this.rotationShape)) {
    return mxEvent.ROTATION_HANDLE;
  } else if (checkShape(this.labelShape)) {
    return mxEvent.LABEL_HANDLE;
  }

  if (this.sizers != null) {
    for (var i = 0; i < this.sizers.length; i++) {
      if (checkShape(this.sizers[i])) {
        return i;
      }
    }
  }

  if (this.customHandles != null && this.isCustomHandleEvent(me)) {
    // Inverse loop order to match display order
    for (var i = this.customHandles.length - 1; i >= 0; i--) {
      if (checkShape(this.customHandles[i].shape)) {
        // LATER: Return reference to active shape
        return mxEvent.CUSTOM_HANDLE - i;
      }
    }
  }

  return null;
};

/**
 * Function: isCustomHandleEvent
 *
 * Returns true if the given event allows custom handles to be changed. This
 * implementation returns true.
 */
mxVertexHandler.prototype.isCustomHandleEvent = function (me) {
  return true;
};

/**
 * Function: mouseDown
 *
 * Handles the event if a handle has been clicked. By consuming the
 * event all subsequent events of the gesture are redirected to this
 * handler.
 */
mxVertexHandler.prototype.mouseDown = function (sender, me) {
  if (!me.isConsumed() && this.graph.isEnabled()) {
    var handle = this.getHandleForEvent(me);

    if (handle != null) {
      this.start(me.getGraphX(), me.getGraphY(), handle);
      me.consume();
    }
  }
};

/**
 * Function: isLivePreviewBorder
 *
 * Called if <livePreview> is enabled to check if a border should be painted.
 * This implementation returns true if the shape is transparent.
 */
mxVertexHandler.prototype.isLivePreviewBorder = function () {
  return this.state.shape != null && this.state.shape.fill == null && this.state.shape.stroke == null;
};

/**
 * Function: start
 *
 * Starts the handling of the mouse gesture.
 */
mxVertexHandler.prototype.start = function (x, y, index) {
  if (this.selectionBorder != null) {
    this.livePreviewActive = this.livePreview && this.graph.model.getChildCount(this.state.cell) == 0;
    this.inTolerance = true;
    this.childOffsetX = 0;
    this.childOffsetY = 0;
    this.index = index;
    this.startX = x;
    this.startY = y;

    if (this.index <= mxEvent.CUSTOM_HANDLE && this.isGhostPreview()) {
      this.ghostPreview = this.createGhostPreview();
    } else {
      // Saves reference to parent state
      var model = this.state.view.graph.model;
      var parent = model.getParent(this.state.cell);

      if (this.state.view.currentRoot != parent && (model.isVertex(parent) || model.isEdge(parent))) {
        this.parentState = this.state.view.graph.view.getState(parent);
      }

      // Creates a preview that can be on top of any HTML label
      this.selectionBorder.node.style.display = index == mxEvent.ROTATION_HANDLE ? 'inline' : 'none';

      // Creates the border that represents the new bounds
      if (!this.livePreviewActive || this.isLivePreviewBorder()) {
        this.preview = this.createSelectionShape(this.bounds);

        if (
          !(mxClient.IS_SVG && Number(this.state.style[mxConstants.STYLE_ROTATION] || '0') != 0) &&
          this.state.text != null &&
          this.state.text.node.parentNode == this.graph.container
        ) {
          this.preview.dialect = mxConstants.DIALECT_STRICTHTML;
          this.preview.init(this.graph.container);
        } else {
          this.preview.dialect =
            this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
          this.preview.init(this.graph.view.getOverlayPane());
        }
      }

      if (index == mxEvent.ROTATION_HANDLE) {
        // With the rotation handle in a corner, need the angle and distance
        var pos = this.getRotationHandlePosition();

        var dx = pos.x - this.state.getCenterX();
        var dy = pos.y - this.state.getCenterY();

        this.startAngle = dx != 0 ? (Math.atan(dy / dx) * 180) / Math.PI + 90 : 0;
        this.startDist = Math.sqrt(dx * dx + dy * dy);
      }

      // Prepares the handles for live preview
      if (this.livePreviewActive) {
        this.hideSizers();

        if (index == mxEvent.ROTATION_HANDLE) {
          this.rotationShape.node.style.display = '';
        } else if (index == mxEvent.LABEL_HANDLE) {
          this.labelShape.node.style.display = '';
        } else if (this.sizers != null && this.sizers[index] != null) {
          this.sizers[index].node.style.display = '';
        } else if (index <= mxEvent.CUSTOM_HANDLE && this.customHandles != null) {
          this.customHandles[mxEvent.CUSTOM_HANDLE - index].setVisible(true);
        }

        // Gets the array of connected edge handlers for redrawing
        var edges = this.graph.getEdges(this.state.cell);
        this.edgeHandlers = [];

        for (var i = 0; i < edges.length; i++) {
          var handler = this.graph.selectionCellsHandler.getHandler(edges[i]);

          if (handler != null) {
            this.edgeHandlers.push(handler);
          }
        }
      }
    }
  }
};

/**
 * Function: createGhostPreview
 *
 * Starts the handling of the mouse gesture.
 */
mxVertexHandler.prototype.createGhostPreview = function () {
  var shape = this.graph.cellRenderer.createShape(this.state);
  shape.init(this.graph.view.getOverlayPane());
  shape.scale = this.state.view.scale;
  shape.bounds = this.bounds;
  shape.outline = true;

  return shape;
};

/**
 * Function: hideHandles
 *
 * Shortcut to <hideSizers>.
 */
mxVertexHandler.prototype.setHandlesVisible = function (visible) {
  this.handlesVisible = visible;

  if (this.sizers != null) {
    for (var i = 0; i < this.sizers.length; i++) {
      this.sizers[i].node.style.display = visible ? '' : 'none';
    }
  }

  if (this.customHandles != null) {
    for (var i = 0; i < this.customHandles.length; i++) {
      this.customHandles[i].setVisible(visible);
    }
  }
};

/**
 * Function: hideSizers
 *
 * Hides all sizers except.
 *
 * Starts the handling of the mouse gesture.
 */
mxVertexHandler.prototype.hideSizers = function () {
  this.setHandlesVisible(false);
};

/**
 * Function: checkTolerance
 *
 * Checks if the coordinates for the given event are within the
 * <mxGraph.tolerance>. If the event is a mouse event then the tolerance is
 * ignored.
 */
mxVertexHandler.prototype.checkTolerance = function (me) {
  if (this.inTolerance && this.startX != null && this.startY != null) {
    if (
      mxEvent.isMouseEvent(me.getEvent()) ||
      Math.abs(me.getGraphX() - this.startX) > this.graph.tolerance ||
      Math.abs(me.getGraphY() - this.startY) > this.graph.tolerance
    ) {
      this.inTolerance = false;
    }
  }
};

/**
 * Function: updateHint
 *
 * Hook for subclassers do show details while the handler is active.
 */
mxVertexHandler.prototype.updateHint = function (me) {};

/**
 * Function: removeHint
 *
 * Hooks for subclassers to hide details when the handler gets inactive.
 */
mxVertexHandler.prototype.removeHint = function () {};

/**
 * Function: roundAngle
 *
 * Hook for rounding the angle. This uses Math.round.
 */
mxVertexHandler.prototype.roundAngle = function (angle) {
  return Math.round(angle * 10) / 10;
};

/**
 * Function: roundLength
 *
 * Hook for rounding the unscaled width or height. This uses Math.round.
 */
mxVertexHandler.prototype.roundLength = function (length) {
  return Math.round(length * 100) / 100;
};

/**
 * Function: mouseMove
 *
 * Handles the event by updating the preview.
 */
mxVertexHandler.prototype.mouseMove = function (sender, me) {
  if (!me.isConsumed() && this.index != null) {
    // Checks tolerance for ignoring single clicks
    this.checkTolerance(me);

    if (!this.inTolerance) {
      if (this.index <= mxEvent.CUSTOM_HANDLE) {
        if (this.customHandles != null) {
          this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);
          this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].active = true;

          if (this.ghostPreview != null) {
            this.ghostPreview.apply(this.state);
            this.ghostPreview.strokewidth =
              this.getSelectionStrokeWidth() / this.ghostPreview.scale / this.ghostPreview.scale;
            this.ghostPreview.isDashed = this.isSelectionDashed();
            this.ghostPreview.stroke = this.getSelectionColor();
            this.ghostPreview.redraw();

            if (this.selectionBounds != null) {
              this.selectionBorder.node.style.display = 'none';
            }
          } else {
            if (this.movePreviewToFront) {
              this.moveToFront();
            }

            this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].positionChanged();
          }
        }
      } else if (this.index == mxEvent.LABEL_HANDLE) {
        this.moveLabel(me);
      } else {
        if (this.index == mxEvent.ROTATION_HANDLE) {
          this.rotateVertex(me);
        } else {
          this.resizeVertex(me);
        }

        this.updateHint(me);
      }
    }

    me.consume();
  }
  // Workaround for disabling the connect highlight when over handle
  else if (!this.graph.isMouseDown && this.getHandleForEvent(me) != null) {
    me.consume(false);
  }
};

/**
 * Function: isGhostPreview
 *
 * Returns true if a ghost preview should be used for custom handles.
 */
mxVertexHandler.prototype.isGhostPreview = function () {
  return this.state.view.graph.model.getChildCount(this.state.cell) > 0;
};

/**
 * Function: moveLabel
 *
 * Moves the label.
 */
mxVertexHandler.prototype.moveLabel = function (me) {
  var point = new mxPoint(me.getGraphX(), me.getGraphY());
  var tr = this.graph.view.translate;
  var scale = this.graph.view.scale;

  if (this.graph.isGridEnabledEvent(me.getEvent())) {
    point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
    point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
  }

  var index = this.rotationShape != null ? this.sizers.length - 2 : this.sizers.length - 1;
  this.moveSizerTo(this.sizers[index], point.x, point.y);
};

/**
 * Function: rotateVertex
 *
 * Rotates the vertex.
 */
mxVertexHandler.prototype.rotateVertex = function (me) {
  var point = new mxPoint(me.getGraphX(), me.getGraphY());
  var dx = this.state.x + this.state.width / 2 - point.x;
  var dy = this.state.y + this.state.height / 2 - point.y;
  this.currentAlpha = dx != 0 ? (Math.atan(dy / dx) * 180) / Math.PI + 90 : dy < 0 ? 180 : 0;

  if (dx > 0) {
    this.currentAlpha -= 180;
  }

  this.currentAlpha -= this.startAngle;

  // Rotation raster
  if (this.rotationRaster && this.graph.isGridEnabledEvent(me.getEvent())) {
    var dx = point.x - this.state.getCenterX();
    var dy = point.y - this.state.getCenterY();
    var dist = Math.sqrt(dx * dx + dy * dy);

    if (dist - this.startDist < 2) {
      raster = 15;
    } else if (dist - this.startDist < 25) {
      raster = 5;
    } else {
      raster = 1;
    }

    this.currentAlpha = Math.round(this.currentAlpha / raster) * raster;
  } else {
    this.currentAlpha = this.roundAngle(this.currentAlpha);
  }

  this.selectionBorder.rotation = this.currentAlpha;
  this.selectionBorder.redraw();

  if (this.livePreviewActive) {
    this.redrawHandles();
  }
};

/**
 * Function: resizeVertex
 *
 * Risizes the vertex.
 */
mxVertexHandler.prototype.resizeVertex = function (me) {
  var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
  var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
  var point = new mxPoint(me.getGraphX(), me.getGraphY());
  var tr = this.graph.view.translate;
  var scale = this.graph.view.scale;
  var cos = Math.cos(-alpha);
  var sin = Math.sin(-alpha);

  var dx = point.x - this.startX;
  var dy = point.y - this.startY;

  // Rotates vector for mouse gesture
  var tx = cos * dx - sin * dy;
  var ty = sin * dx + cos * dy;

  dx = tx;
  dy = ty;

  var geo = this.graph.getCellGeometry(this.state.cell);
  this.unscaledBounds = this.union(
    geo,
    dx / scale,
    dy / scale,
    this.index,
    this.graph.isGridEnabledEvent(me.getEvent()),
    1,
    new mxPoint(0, 0),
    this.isConstrainedEvent(me),
    this.isCenteredEvent(this.state, me)
  );

  // Keeps vertex within maximum graph or parent bounds
  if (!geo.relative) {
    var max = this.graph.getMaximumGraphBounds();

    // Handles child cells
    if (max != null && this.parentState != null) {
      max = mxRectangle.fromRectangle(max);

      max.x -= (this.parentState.x - tr.x * scale) / scale;
      max.y -= (this.parentState.y - tr.y * scale) / scale;
    }

    if (this.graph.isConstrainChild(this.state.cell)) {
      var tmp = this.graph.getCellContainmentArea(this.state.cell);

      if (tmp != null) {
        var overlap = this.graph.getOverlap(this.state.cell);

        if (overlap > 0) {
          tmp = mxRectangle.fromRectangle(tmp);

          tmp.x -= tmp.width * overlap;
          tmp.y -= tmp.height * overlap;
          tmp.width += 2 * tmp.width * overlap;
          tmp.height += 2 * tmp.height * overlap;
        }

        if (max == null) {
          max = tmp;
        } else {
          max = mxRectangle.fromRectangle(max);
          max.intersect(tmp);
        }
      }
    }

    if (max != null) {
      if (this.unscaledBounds.x < max.x) {
        this.unscaledBounds.width -= max.x - this.unscaledBounds.x;
        this.unscaledBounds.x = max.x;
      }

      if (this.unscaledBounds.y < max.y) {
        this.unscaledBounds.height -= max.y - this.unscaledBounds.y;
        this.unscaledBounds.y = max.y;
      }

      if (this.unscaledBounds.x + this.unscaledBounds.width > max.x + max.width) {
        this.unscaledBounds.width -= this.unscaledBounds.x + this.unscaledBounds.width - max.x - max.width;
      }

      if (this.unscaledBounds.y + this.unscaledBounds.height > max.y + max.height) {
        this.unscaledBounds.height -= this.unscaledBounds.y + this.unscaledBounds.height - max.y - max.height;
      }
    }
  }

  var old = this.bounds;
  this.bounds = new mxRectangle(
    (this.parentState != null ? this.parentState.x : tr.x * scale) + this.unscaledBounds.x * scale,
    (this.parentState != null ? this.parentState.y : tr.y * scale) + this.unscaledBounds.y * scale,
    this.unscaledBounds.width * scale,
    this.unscaledBounds.height * scale
  );

  if (geo.relative && this.parentState != null) {
    this.bounds.x += this.state.x - this.parentState.x;
    this.bounds.y += this.state.y - this.parentState.y;
  }

  cos = Math.cos(alpha);
  sin = Math.sin(alpha);

  var c2 = new mxPoint(this.bounds.getCenterX(), this.bounds.getCenterY());

  var dx = c2.x - ct.x;
  var dy = c2.y - ct.y;

  var dx2 = cos * dx - sin * dy;
  var dy2 = sin * dx + cos * dy;

  var dx3 = dx2 - dx;
  var dy3 = dy2 - dy;

  var dx4 = this.bounds.x - this.state.x;
  var dy4 = this.bounds.y - this.state.y;

  var dx5 = cos * dx4 - sin * dy4;
  var dy5 = sin * dx4 + cos * dy4;

  this.bounds.x += dx3;
  this.bounds.y += dy3;

  // Rounds unscaled bounds to int
  this.unscaledBounds.x = this.roundLength(this.unscaledBounds.x + dx3 / scale);
  this.unscaledBounds.y = this.roundLength(this.unscaledBounds.y + dy3 / scale);
  this.unscaledBounds.width = this.roundLength(this.unscaledBounds.width);
  this.unscaledBounds.height = this.roundLength(this.unscaledBounds.height);

  // Shifts the children according to parent offset
  if (!this.graph.isCellCollapsed(this.state.cell) && (dx3 != 0 || dy3 != 0)) {
    this.childOffsetX = this.state.x - this.bounds.x + dx5;
    this.childOffsetY = this.state.y - this.bounds.y + dy5;
  } else {
    this.childOffsetX = 0;
    this.childOffsetY = 0;
  }

  if (!old.equals(this.bounds)) {
    if (this.livePreviewActive) {
      this.updateLivePreview(me);
    }

    if (this.preview != null) {
      this.drawPreview();
    } else {
      this.updateParentHighlight();
    }
  }
};

/**
 * Function: updateLivePreview
 *
 * Repaints the live preview.
 */
mxVertexHandler.prototype.updateLivePreview = function (me) {
  // TODO: Apply child offset to children in live preview
  var scale = this.graph.view.scale;
  var tr = this.graph.view.translate;

  // Saves current state
  var tempState = this.state.clone();

  // Temporarily changes size and origin
  this.state.x = this.bounds.x;
  this.state.y = this.bounds.y;
  this.state.origin = new mxPoint(this.state.x / scale - tr.x, this.state.y / scale - tr.y);
  this.state.width = this.bounds.width;
  this.state.height = this.bounds.height;

  // Redraws cell and handles
  var off = this.state.absoluteOffset;
  off = new mxPoint(off.x, off.y);

  // Required to store and reset absolute offset for updating label position
  this.state.absoluteOffset.x = 0;
  this.state.absoluteOffset.y = 0;
  var geo = this.graph.getCellGeometry(this.state.cell);

  if (geo != null) {
    var offset = geo.offset || this.EMPTY_POINT;

    if (offset != null && !geo.relative) {
      this.state.absoluteOffset.x = this.state.view.scale * offset.x;
      this.state.absoluteOffset.y = this.state.view.scale * offset.y;
    }

    this.state.view.updateVertexLabelOffset(this.state);
  }

  // Draws the live preview
  this.state.view.graph.cellRenderer.redraw(this.state, true);

  // Redraws connected edges TODO: Include child edges
  this.state.view.invalidate(this.state.cell);
  this.state.invalid = false;
  this.state.view.validate();
  this.redrawHandles();

  // Moves live preview to front
  if (this.movePreviewToFront) {
    this.moveToFront();
  }

  // Hides folding icon
  if (this.state.control != null && this.state.control.node != null) {
    this.state.control.node.style.visibility = 'hidden';
  }

  // Restores current state
  this.state.setState(tempState);
};

/**
 * Function: moveToFront
 *
 * Handles the event by applying the changes to the geometry.
 */
mxVertexHandler.prototype.moveToFront = function () {
  if (
    (this.state.text != null && this.state.text.node != null && this.state.text.node.nextSibling != null) ||
    (this.state.shape != null &&
      this.state.shape.node != null &&
      this.state.shape.node.nextSibling != null &&
      (this.state.text == null || this.state.shape.node.nextSibling != this.state.text.node))
  ) {
    if (this.state.shape != null && this.state.shape.node != null) {
      this.state.shape.node.parentNode.appendChild(this.state.shape.node);
    }

    if (this.state.text != null && this.state.text.node != null) {
      this.state.text.node.parentNode.appendChild(this.state.text.node);
    }
  }
};

/**
 * Function: mouseUp
 *
 * Handles the event by applying the changes to the geometry.
 */
mxVertexHandler.prototype.mouseUp = function (sender, me) {
  if (this.index != null && this.state != null) {
    var point = new mxPoint(me.getGraphX(), me.getGraphY());
    var index = this.index;
    this.index = null;

    if (this.ghostPreview == null) {
      // Required to restore order in case of no change
      this.state.view.invalidate(this.state.cell, false, false);
      this.state.view.validate();
    }

    this.graph.getModel().beginUpdate();
    try {
      if (index <= mxEvent.CUSTOM_HANDLE) {
        if (this.customHandles != null) {
          // Creates style before changing cell state
          var style = this.state.view.graph.getCellStyle(this.state.cell);

          this.customHandles[mxEvent.CUSTOM_HANDLE - index].active = false;
          this.customHandles[mxEvent.CUSTOM_HANDLE - index].execute(me);

          // Sets style and apply on shape to force repaint and
          // check if execute has removed custom handles
          if (this.customHandles != null && this.customHandles[mxEvent.CUSTOM_HANDLE - index] != null) {
            this.state.style = style;
            this.customHandles[mxEvent.CUSTOM_HANDLE - index].positionChanged();
          }
        }
      } else if (index == mxEvent.ROTATION_HANDLE) {
        if (this.currentAlpha != null) {
          var delta = this.currentAlpha - (this.state.style[mxConstants.STYLE_ROTATION] || 0);

          if (delta != 0) {
            this.rotateCell(this.state.cell, delta);
          }
        } else {
          this.rotateClick();
        }
      } else {
        var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
        var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
        var cos = Math.cos(-alpha);
        var sin = Math.sin(-alpha);

        var dx = point.x - this.startX;
        var dy = point.y - this.startY;

        // Rotates vector for mouse gesture
        var tx = cos * dx - sin * dy;
        var ty = sin * dx + cos * dy;

        dx = tx;
        dy = ty;

        var s = this.graph.view.scale;
        var recurse = this.isRecursiveResize(this.state, me);
        this.resizeCell(
          this.state.cell,
          this.roundLength(dx / s),
          this.roundLength(dy / s),
          index,
          gridEnabled,
          this.isConstrainedEvent(me),
          recurse
        );
      }
    } finally {
      this.graph.getModel().endUpdate();
    }

    me.consume();
    this.reset();
    this.redrawHandles();
  }
};

/**
 * Function: isRecursiveResize
 *
 * Returns the recursiveResize of the give state.
 *
 * Parameters:
 *
 * state - the given <mxCellState>. This implementation takes
 * the value of this state.
 * me - the mouse event.
 */
mxVertexHandler.prototype.isRecursiveResize = function (state, me) {
  return this.graph.isRecursiveResize(this.state);
};

/**
 * Function: rotateClick
 *
 * Hook for subclassers to implement a single click on the rotation handle.
 * This code is executed as part of the model transaction. This implementation
 * is empty.
 */
mxVertexHandler.prototype.rotateClick = function () {};

/**
 * Function: rotateCell
 *
 * Rotates the given cell and its children by the given angle in degrees.
 *
 * Parameters:
 *
 * cell - <mxCell> to be rotated.
 * angle - Angle in degrees.
 */
mxVertexHandler.prototype.rotateCell = function (cell, angle, parent) {
  if (angle != 0) {
    var model = this.graph.getModel();

    if (model.isVertex(cell) || model.isEdge(cell)) {
      if (!model.isEdge(cell)) {
        var style = this.graph.getCurrentCellStyle(cell);
        var total = (style[mxConstants.STYLE_ROTATION] || 0) + angle;
        this.graph.setCellStyles(mxConstants.STYLE_ROTATION, total, [cell]);
      }

      var geo = this.graph.getCellGeometry(cell);

      if (geo != null) {
        var pgeo = this.graph.getCellGeometry(parent);

        if (pgeo != null && !model.isEdge(parent)) {
          geo = geo.clone();
          geo.rotate(angle, new mxPoint(pgeo.width / 2, pgeo.height / 2));
          model.setGeometry(cell, geo);
        }

        if ((model.isVertex(cell) && !geo.relative) || model.isEdge(cell)) {
          // Recursive rotation
          var childCount = model.getChildCount(cell);

          for (var i = 0; i < childCount; i++) {
            this.rotateCell(model.getChildAt(cell, i), angle, cell);
          }
        }
      }
    }
  }
};

/**
 * Function: reset
 *
 * Resets the state of this handler.
 */
mxVertexHandler.prototype.reset = function () {
  if (
    this.sizers != null &&
    this.index != null &&
    this.sizers[this.index] != null &&
    this.sizers[this.index].node.style.display == 'none'
  ) {
    this.sizers[this.index].node.style.display = '';
  }

  this.currentAlpha = null;
  this.inTolerance = null;
  this.index = null;

  // TODO: Reset and redraw cell states for live preview
  if (this.preview != null) {
    this.preview.destroy();
    this.preview = null;
  }

  if (this.ghostPreview != null) {
    this.ghostPreview.destroy();
    this.ghostPreview = null;
  }

  if (this.livePreviewActive && this.sizers != null) {
    for (var i = 0; i < this.sizers.length; i++) {
      if (this.sizers[i] != null) {
        this.sizers[i].node.style.display = '';
      }
    }

    // Shows folding icon
    if (this.state.control != null && this.state.control.node != null) {
      this.state.control.node.style.visibility = '';
    }
  }

  if (this.customHandles != null) {
    for (var i = 0; i < this.customHandles.length; i++) {
      if (this.customHandles[i].active) {
        this.customHandles[i].active = false;
        this.customHandles[i].reset();
      } else {
        this.customHandles[i].setVisible(true);
      }
    }
  }

  // Checks if handler has been destroyed
  if (this.selectionBorder != null) {
    this.selectionBorder.node.style.display = 'inline';
    this.selectionBounds = this.getSelectionBounds(this.state);
    this.bounds = new mxRectangle(
      this.selectionBounds.x,
      this.selectionBounds.y,
      this.selectionBounds.width,
      this.selectionBounds.height
    );
    this.drawPreview();
  }

  this.removeHint();
  this.redrawHandles();
  this.edgeHandlers = null;
  this.handlesVisible = true;
  this.unscaledBounds = null;
  this.livePreviewActive = null;
};

/**
 * Function: resizeCell
 *
 * Uses the given vector to change the bounds of the given cell
 * in the graph using <mxGraph.resizeCell>.
 */
mxVertexHandler.prototype.resizeCell = function (cell, dx, dy, index, gridEnabled, constrained, recurse) {
  var geo = this.graph.model.getGeometry(cell);

  if (geo != null) {
    if (index == mxEvent.LABEL_HANDLE) {
      var alpha = -mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
      var cos = Math.cos(alpha);
      var sin = Math.sin(alpha);
      var scale = this.graph.view.scale;
      var pt = mxUtils.getRotatedPoint(
        new mxPoint(
          Math.round((this.labelShape.bounds.getCenterX() - this.startX) / scale),
          Math.round((this.labelShape.bounds.getCenterY() - this.startY) / scale)
        ),
        cos,
        sin
      );

      geo = geo.clone();

      if (geo.offset == null) {
        geo.offset = pt;
      } else {
        geo.offset.x += pt.x;
        geo.offset.y += pt.y;
      }

      this.graph.model.setGeometry(cell, geo);
    } else if (this.unscaledBounds != null) {
      var scale = this.graph.view.scale;

      if (this.childOffsetX != 0 || this.childOffsetY != 0) {
        this.moveChildren(cell, Math.round(this.childOffsetX / scale), Math.round(this.childOffsetY / scale));
      }

      this.graph.resizeCell(cell, this.unscaledBounds, recurse);
    }
  }
};

/**
 * Function: moveChildren
 *
 * Moves the children of the given cell by the given vector.
 */
mxVertexHandler.prototype.moveChildren = function (cell, dx, dy) {
  var model = this.graph.getModel();
  var childCount = model.getChildCount(cell);

  for (var i = 0; i < childCount; i++) {
    var child = model.getChildAt(cell, i);
    var geo = this.graph.getCellGeometry(child);

    if (geo != null) {
      geo = geo.clone();
      geo.translate(dx, dy);
      model.setGeometry(child, geo);
    }
  }
};
/**
 * Function: union
 *
 * Returns the union of the given bounds and location for the specified
 * handle index.
 *
 * To override this to limit the size of vertex via a minWidth/-Height style,
 * the following code can be used.
 *
 * (code)
 * var vertexHandlerUnion = mxVertexHandler.prototype.union;
 * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)
 * {
 *   var result = vertexHandlerUnion.apply(this, arguments);
 *
 *   result.width = Math.max(result.width, mxUtils.getNumber(this.state.style, 'minWidth', 0));
 *   result.height = Math.max(result.height, mxUtils.getNumber(this.state.style, 'minHeight', 0));
 *
 *   return result;
 * };
 * (end)
 *
 * The minWidth/-Height style can then be used as follows:
 *
 * (code)
 * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'minWidth=100;minHeight=100;');
 * (end)
 *
 * To override this to update the height for a wrapped text if the width of a vertex is
 * changed, the following can be used.
 *
 * (code)
 * var mxVertexHandlerUnion = mxVertexHandler.prototype.union;
 * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr, constrained)
 * {
 *   var result = mxVertexHandlerUnion.apply(this, arguments);
 *   var s = this.state;
 *
 *   if (this.graph.isHtmlLabel(s.cell) && (index == 3 || index == 4) &&
 *       s.text != null && s.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap')
 *   {
 *     var label = this.graph.getLabel(s.cell);
 *     var fontSize = mxUtils.getNumber(s.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
 *     var ww = result.width / s.view.scale - s.text.spacingRight - s.text.spacingLeft
 *
 *     result.height = mxUtils.getSizeForString(label, fontSize, s.style[mxConstants.STYLE_FONTFAMILY], ww).height;
 *   }
 *
 *   return result;
 * };
 * (end)
 */
mxVertexHandler.prototype.union = function (bounds, dx, dy, index, gridEnabled, scale, tr, constrained, centered) {
  gridEnabled = gridEnabled != null ? gridEnabled && this.graph.gridEnabled : this.graph.gridEnabled;

  if (this.singleSizer) {
    var x = bounds.x + bounds.width + dx;
    var y = bounds.y + bounds.height + dy;

    if (gridEnabled) {
      x = this.graph.snap(x / scale) * scale;
      y = this.graph.snap(y / scale) * scale;
    }

    var rect = new mxRectangle(bounds.x, bounds.y, 0, 0);
    rect.add(new mxRectangle(x, y, 0, 0));

    return rect;
  } else {
    var w0 = bounds.width;
    var h0 = bounds.height;
    var left = bounds.x - tr.x * scale;
    var right = left + w0;
    var top = bounds.y - tr.y * scale;
    var bottom = top + h0;

    var cx = left + w0 / 2;
    var cy = top + h0 / 2;

    if (index > 4 /* Bottom Row */) {
      bottom = bottom + dy;

      if (gridEnabled) {
        bottom = this.graph.snap(bottom / scale) * scale;
      } else {
        bottom = Math.round(bottom / scale) * scale;
      }
    } else if (index < 3 /* Top Row */) {
      top = top + dy;

      if (gridEnabled) {
        top = this.graph.snap(top / scale) * scale;
      } else {
        top = Math.round(top / scale) * scale;
      }
    }

    if (index == 0 || index == 3 || index == 5 /* Left */) {
      left += dx;

      if (gridEnabled) {
        left = this.graph.snap(left / scale) * scale;
      } else {
        left = Math.round(left / scale) * scale;
      }
    } else if (index == 2 || index == 4 || index == 7 /* Right */) {
      right += dx;

      if (gridEnabled) {
        right = this.graph.snap(right / scale) * scale;
      } else {
        right = Math.round(right / scale) * scale;
      }
    }

    var width = right - left;
    var height = bottom - top;

    if (constrained) {
      var geo = this.graph.getCellGeometry(this.state.cell);

      if (geo != null) {
        var aspect = geo.width / geo.height;

        if (index == 1 || index == 2 || index == 7 || index == 6) {
          width = height * aspect;
        } else {
          height = width / aspect;
        }

        if (index == 0) {
          left = right - width;
          top = bottom - height;
        }
      }
    }

    if (centered) {
      width += width - w0;
      height += height - h0;

      var cdx = cx - (left + width / 2);
      var cdy = cy - (top + height / 2);

      left += cdx;
      top += cdy;
      right += cdx;
      bottom += cdy;
    }

    // Flips over left side
    if (width < 0) {
      left += width;
      width = Math.abs(width);
    }

    // Flips over top side
    if (height < 0) {
      top += height;
      height = Math.abs(height);
    }

    var result = new mxRectangle(left + tr.x * scale, top + tr.y * scale, width, height);

    if (this.minBounds != null) {
      result.width = Math.max(
        result.width,
        this.minBounds.x * scale + this.minBounds.width * scale + Math.max(0, this.x0 * scale - result.x)
      );
      result.height = Math.max(
        result.height,
        this.minBounds.y * scale + this.minBounds.height * scale + Math.max(0, this.y0 * scale - result.y)
      );
    }

    return result;
  }
};

/**
 * Function: redraw
 *
 * Redraws the handles and the preview.
 */
mxVertexHandler.prototype.redraw = function (ignoreHandles) {
  this.selectionBounds = this.getSelectionBounds(this.state);
  this.bounds = new mxRectangle(
    this.selectionBounds.x,
    this.selectionBounds.y,
    this.selectionBounds.width,
    this.selectionBounds.height
  );
  this.drawPreview();

  if (!ignoreHandles) {
    this.redrawHandles();
  }
};

/**
 * Returns the padding to be used for drawing handles for the current <bounds>.
 */
mxVertexHandler.prototype.getHandlePadding = function () {
  // KNOWN: Tolerance depends on event type (eg. 0 for mouse events)
  var result = new mxPoint(0, 0);
  var tol = this.tolerance;

  if (
    this.sizers != null &&
    this.sizers.length > 0 &&
    this.sizers[0] != null &&
    (this.bounds.width < 2 * this.sizers[0].bounds.width + 2 * tol ||
      this.bounds.height < 2 * this.sizers[0].bounds.height + 2 * tol)
  ) {
    tol /= 2;

    result.x = this.sizers[0].bounds.width + tol;
    result.y = this.sizers[0].bounds.height + tol;
  }

  return result;
};

/**
 * Function: getSizerBounds
 *
 * Returns the bounds used to paint the resize handles.
 */
mxVertexHandler.prototype.getSizerBounds = function () {
  return this.bounds;
};

/**
 * Function: redrawHandles
 *
 * Redraws the handles. To hide certain handles the following code can be used.
 *
 * (code)
 * mxVertexHandler.prototype.redrawHandles = function()
 * {
 *   mxVertexHandlerRedrawHandles.apply(this, arguments);
 *
 *   if (this.sizers != null && this.sizers.length > 7)
 *   {
 *     this.sizers[1].node.style.display = 'none';
 *     this.sizers[6].node.style.display = 'none';
 *   }
 * };
 * (end)
 */
mxVertexHandler.prototype.redrawHandles = function () {
  var s = this.getSizerBounds();
  var tol = this.tolerance;
  this.horizontalOffset = 0;
  this.verticalOffset = 0;

  if (this.customHandles != null) {
    for (var i = 0; i < this.customHandles.length; i++) {
      var temp = this.customHandles[i].shape.node.style.display;
      this.customHandles[i].redraw();
      this.customHandles[i].shape.node.style.display = temp;

      // Hides custom handles during text editing
      this.customHandles[i].shape.node.style.visibility =
        this.handlesVisible && this.isCustomHandleVisible(this.customHandles[i]) ? '' : 'hidden';
    }
  }

  if (this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null) {
    if (this.index == null && this.manageSizers && this.sizers.length >= 8) {
      // KNOWN: Tolerance depends on event type (eg. 0 for mouse events)
      var padding = this.getHandlePadding();
      this.horizontalOffset = padding.x;
      this.verticalOffset = padding.y;

      if (this.horizontalOffset != 0 || this.verticalOffset != 0) {
        s = new mxRectangle(s.x, s.y, s.width, s.height);

        s.x -= this.horizontalOffset / 2;
        s.width += this.horizontalOffset;
        s.y -= this.verticalOffset / 2;
        s.height += this.verticalOffset;
      }

      if (this.sizers.length >= 8) {
        if (
          s.width < 2 * this.sizers[0].bounds.width + 2 * tol ||
          s.height < 2 * this.sizers[0].bounds.height + 2 * tol
        ) {
          this.sizers[0].node.style.display = 'none';
          this.sizers[2].node.style.display = 'none';
          this.sizers[5].node.style.display = 'none';
          this.sizers[7].node.style.display = 'none';
        } else if (this.handlesVisible) {
          this.sizers[0].node.style.display = '';
          this.sizers[2].node.style.display = '';
          this.sizers[5].node.style.display = '';
          this.sizers[7].node.style.display = '';
        }
      }
    }

    var r = s.x + s.width;
    var b = s.y + s.height;

    if (this.singleSizer) {
      this.moveSizerTo(this.sizers[0], r, b);
    } else {
      var cx = s.x + s.width / 2;
      var cy = s.y + s.height / 2;

      if (this.sizers.length >= 8) {
        var crs = ['nw-resize', 'n-resize', 'ne-resize', 'e-resize', 'se-resize', 's-resize', 'sw-resize', 'w-resize'];

        var alpha = mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION] || '0');
        var cos = Math.cos(alpha);
        var sin = Math.sin(alpha);

        var da = Math.round((alpha * 4) / Math.PI);

        var ct = new mxPoint(s.getCenterX(), s.getCenterY());
        var pt = mxUtils.getRotatedPoint(new mxPoint(s.x, s.y), cos, sin, ct);

        this.moveSizerTo(this.sizers[0], pt.x, pt.y);
        this.sizers[0].setCursor(crs[mxUtils.mod(0 + da, crs.length)]);

        pt.x = cx;
        pt.y = s.y;
        pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

        this.moveSizerTo(this.sizers[1], pt.x, pt.y);
        this.sizers[1].setCursor(crs[mxUtils.mod(1 + da, crs.length)]);

        pt.x = r;
        pt.y = s.y;
        pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

        this.moveSizerTo(this.sizers[2], pt.x, pt.y);
        this.sizers[2].setCursor(crs[mxUtils.mod(2 + da, crs.length)]);

        pt.x = s.x;
        pt.y = cy;
        pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

        this.moveSizerTo(this.sizers[3], pt.x, pt.y);
        this.sizers[3].setCursor(crs[mxUtils.mod(7 + da, crs.length)]);

        pt.x = r;
        pt.y = cy;
        pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

        this.moveSizerTo(this.sizers[4], pt.x, pt.y);
        this.sizers[4].setCursor(crs[mxUtils.mod(3 + da, crs.length)]);

        pt.x = s.x;
        pt.y = b;
        pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

        this.moveSizerTo(this.sizers[5], pt.x, pt.y);
        this.sizers[5].setCursor(crs[mxUtils.mod(6 + da, crs.length)]);

        pt.x = cx;
        pt.y = b;
        pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

        this.moveSizerTo(this.sizers[6], pt.x, pt.y);
        this.sizers[6].setCursor(crs[mxUtils.mod(5 + da, crs.length)]);

        pt.x = r;
        pt.y = b;
        pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);

        this.moveSizerTo(this.sizers[7], pt.x, pt.y);
        this.sizers[7].setCursor(crs[mxUtils.mod(4 + da, crs.length)]);

        pt.x = cx + this.state.absoluteOffset.x;
        pt.y = cy + this.state.absoluteOffset.y;
        pt = mxUtils.getRotatedPoint(pt, cos, sin, ct);
        this.moveSizerTo(this.sizers[8], pt.x, pt.y);
      } else if (this.state.width >= 2 && this.state.height >= 2) {
        this.moveSizerTo(this.sizers[0], cx + this.state.absoluteOffset.x, cy + this.state.absoluteOffset.y);
      } else {
        this.moveSizerTo(this.sizers[0], this.state.x, this.state.y);
      }
    }
  }

  if (this.rotationShape != null) {
    var alpha = mxUtils.toRadians(
      this.currentAlpha != null ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0'
    );
    var cos = Math.cos(alpha);
    var sin = Math.sin(alpha);

    var ct = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
    var pt = mxUtils.getRotatedPoint(this.getRotationHandlePosition(), cos, sin, ct);

    if (this.rotationShape.node != null) {
      this.moveSizerTo(this.rotationShape, pt.x, pt.y);

      // Hides rotation handle during text editing
      this.rotationShape.node.style.visibility =
        this.state.view.graph.isEditing() || !this.handlesVisible ? 'hidden' : '';
    }
  }

  if (this.selectionBorder != null) {
    this.selectionBorder.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
  }

  if (this.edgeHandlers != null) {
    for (var i = 0; i < this.edgeHandlers.length; i++) {
      this.edgeHandlers[i].redraw();
    }
  }
};

/**
 * Function: isCustomHandleVisible
 *
 * Returns true if the given custom handle is visible.
 */
mxVertexHandler.prototype.isCustomHandleVisible = function (handle) {
  return !this.graph.isEditing() && this.state.view.graph.getSelectionCount() == 1;
};

/**
 * Function: getRotationHandlePosition
 *
 * Returns an <mxPoint> that defines the rotation handle position.
 */
mxVertexHandler.prototype.getRotationHandlePosition = function () {
  return new mxPoint(this.bounds.x + this.bounds.width / 2, this.bounds.y + this.rotationHandleVSpacing);
};

/**
 * Function: isParentHighlightVisible
 *
 * Returns true if the parent highlight should be visible. This implementation
 * always returns true.
 */
mxVertexHandler.prototype.isParentHighlightVisible = function () {
  return !this.graph.isCellSelected(this.graph.model.getParent(this.state.cell));
};

/**
 * Function: updateParentHighlight
 *
 * Updates the highlight of the parent if <parentHighlightEnabled> is true.
 */
mxVertexHandler.prototype.updateParentHighlight = function () {
  if (!this.isDestroyed()) {
    var visible = this.isParentHighlightVisible();
    var parent = this.graph.model.getParent(this.state.cell);
    var pstate = this.graph.view.getState(parent);

    if (this.parentHighlight != null) {
      if (this.graph.model.isVertex(parent) && visible) {
        var b = this.parentHighlight.bounds;

        if (
          pstate != null &&
          (b.x != pstate.x || b.y != pstate.y || b.width != pstate.width || b.height != pstate.height)
        ) {
          this.parentHighlight.bounds = mxRectangle.fromRectangle(pstate);
          this.parentHighlight.redraw();
        }
      } else {
        if (pstate != null && pstate.parentHighlight == this.parentHighlight) {
          pstate.parentHighlight = null;
        }

        this.parentHighlight.destroy();
        this.parentHighlight = null;
      }
    } else if (this.parentHighlightEnabled && visible) {
      if (this.graph.model.isVertex(parent) && pstate != null && pstate.parentHighlight == null) {
        this.parentHighlight = this.createParentHighlightShape(pstate);
        // VML dialect required here for event transparency in IE
        this.parentHighlight.dialect =
          this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
        this.parentHighlight.pointerEvents = false;
        this.parentHighlight.rotation = Number(pstate.style[mxConstants.STYLE_ROTATION] || '0');
        this.parentHighlight.init(this.graph.getView().getOverlayPane());
        this.parentHighlight.redraw();

        // Shows highlight once per parent
        pstate.parentHighlight = this.parentHighlight;
      }
    }
  }
};

/**
 * Function: drawPreview
 *
 * Redraws the preview.
 */
mxVertexHandler.prototype.drawPreview = function () {
  if (this.preview != null) {
    this.preview.bounds = this.bounds;

    if (this.preview.node.parentNode == this.graph.container) {
      this.preview.bounds.width = Math.max(0, this.preview.bounds.width - 1);
      this.preview.bounds.height = Math.max(0, this.preview.bounds.height - 1);
    }

    this.preview.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
    this.preview.redraw();
  }

  this.selectionBorder.bounds = this.getSelectionBorderBounds();
  this.selectionBorder.redraw();
  this.updateParentHighlight();
};

/**
 * Function: getSelectionBorderBounds
 *
 * Returns the bounds for the selection border.
 */
mxVertexHandler.prototype.getSelectionBorderBounds = function () {
  return this.bounds;
};

/**
 * Function: isDestroyed
 *
 * Returns true if this handler was destroyed or not initialized.
 */
mxVertexHandler.prototype.isDestroyed = function () {
  return this.selectionBorder == null;
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes.
 */
mxVertexHandler.prototype.destroy = function () {
  if (this.escapeHandler != null) {
    this.state.view.graph.removeListener(this.escapeHandler);
    this.escapeHandler = null;
  }

  if (this.preview != null) {
    this.preview.destroy();
    this.preview = null;
  }

  if (this.parentHighlight != null) {
    var parent = this.graph.model.getParent(this.state.cell);
    var pstate = this.graph.view.getState(parent);

    if (pstate != null && pstate.parentHighlight == this.parentHighlight) {
      pstate.parentHighlight = null;
    }

    this.parentHighlight.destroy();
    this.parentHighlight = null;
  }

  if (this.ghostPreview != null) {
    this.ghostPreview.destroy();
    this.ghostPreview = null;
  }

  if (this.selectionBorder != null) {
    this.selectionBorder.destroy();
    this.selectionBorder = null;
  }

  this.labelShape = null;
  this.removeHint();

  if (this.sizers != null) {
    for (var i = 0; i < this.sizers.length; i++) {
      this.sizers[i].destroy();
    }

    this.sizers = null;
  }

  if (this.customHandles != null) {
    for (var i = 0; i < this.customHandles.length; i++) {
      this.customHandles[i].destroy();
    }

    this.customHandles = null;
  }
};

/**
 * handler/mxEdgeHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxEdgeHandler
 *
 * Graph event handler that reconnects edges and modifies control points and
 * the edge label location. Uses <mxTerminalMarker> for finding and
 * highlighting new source and target vertices. This handler is automatically
 * created in <mxGraph.createHandler> for each selected edge.
 *
 * To enable adding/removing control points, the following code can be used:
 *
 * (code)
 * mxEdgeHandler.prototype.addEnabled = true;
 * mxEdgeHandler.prototype.removeEnabled = true;
 * (end)
 *
 * Note: This experimental feature is not recommended for production use.
 *
 * Constructor: mxEdgeHandler
 *
 * Constructs an edge handler for the specified <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> of the cell to be handled.
 */
function mxEdgeHandler(state) {
  if (state != null && state.shape != null) {
    this.state = state;
    this.init();

    // Handles escape keystrokes
    this.escapeHandler = mxUtils.bind(this, function (sender, evt) {
      var dirty = this.index != null;
      this.reset();

      if (dirty) {
        this.graph.cellRenderer.redraw(this.state, false, state.view.isRendering());
      }
    });

    this.state.view.graph.addListener(mxEvent.ESCAPE, this.escapeHandler);
  }
}

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxEdgeHandler.prototype.graph = null;

/**
 * Variable: state
 *
 * Reference to the <mxCellState> being modified.
 */
mxEdgeHandler.prototype.state = null;

/**
 * Variable: marker
 *
 * Holds the <mxTerminalMarker> which is used for highlighting terminals.
 */
mxEdgeHandler.prototype.marker = null;

/**
 * Variable: constraintHandler
 *
 * Holds the <mxConstraintHandler> used for drawing and highlighting
 * constraints.
 */
mxEdgeHandler.prototype.constraintHandler = null;

/**
 * Variable: error
 *
 * Holds the current validation error while a connection is being changed.
 */
mxEdgeHandler.prototype.error = null;

/**
 * Variable: shape
 *
 * Holds the <mxShape> that represents the preview edge.
 */
mxEdgeHandler.prototype.shape = null;

/**
 * Variable: bends
 *
 * Holds the <mxShapes> that represent the points.
 */
mxEdgeHandler.prototype.bends = null;

/**
 * Variable: labelShape
 *
 * Holds the <mxShape> that represents the label position.
 */
mxEdgeHandler.prototype.labelShape = null;

/**
 * Variable: cloneEnabled
 *
 * Specifies if cloning by control-drag is enabled. Default is true.
 */
mxEdgeHandler.prototype.cloneEnabled = true;

/**
 * Variable: addEnabled
 *
 * Specifies if adding bends by shift-click is enabled. Default is false.
 * Note: This experimental feature is not recommended for production use.
 */
mxEdgeHandler.prototype.addEnabled = false;

/**
 * Variable: removeEnabled
 *
 * Specifies if removing bends by shift-click is enabled. Default is false.
 * Note: This experimental feature is not recommended for production use.
 */
mxEdgeHandler.prototype.removeEnabled = false;

/**
 * Variable: dblClickRemoveEnabled
 *
 * Specifies if removing bends by double click is enabled. Default is false.
 */
mxEdgeHandler.prototype.dblClickRemoveEnabled = false;

/**
 * Variable: mergeRemoveEnabled
 *
 * Specifies if removing bends by dropping them on other bends is enabled.
 * Default is false.
 */
mxEdgeHandler.prototype.mergeRemoveEnabled = false;

/**
 * Variable: straightRemoveEnabled
 *
 * Specifies if removing bends by creating straight segments should be enabled.
 * If enabled, this can be overridden by holding down the alt key while moving.
 * Default is false.
 */
mxEdgeHandler.prototype.straightRemoveEnabled = false;

/**
 * Variable: virtualBendsEnabled
 *
 * Specifies if virtual bends should be added in the center of each
 * segments. These bends can then be used to add new waypoints.
 * Default is false.
 */
mxEdgeHandler.prototype.virtualBendsEnabled = false;

/**
 * Variable: virtualBendOpacity
 *
 * Opacity to be used for virtual bends (see <virtualBendsEnabled>).
 * Default is 20.
 */
mxEdgeHandler.prototype.virtualBendOpacity = 20;

/**
 * Variable: parentHighlightEnabled
 *
 * Specifies if the parent should be highlighted if a child cell is selected.
 * Default is false.
 */
mxEdgeHandler.prototype.parentHighlightEnabled = false;

/**
 * Variable: preferHtml
 *
 * Specifies if bends should be added to the graph container. This is updated
 * in <init> based on whether the edge or one of its terminals has an HTML
 * label in the container.
 */
mxEdgeHandler.prototype.preferHtml = false;

/**
 * Variable: allowHandleBoundsCheck
 *
 * Specifies if the bounds of handles should be used for hit-detection in IE
 * Default is true.
 */
mxEdgeHandler.prototype.allowHandleBoundsCheck = true;

/**
 * Variable: snapToTerminals
 *
 * Specifies if waypoints should snap to the routing centers of terminals.
 * Default is false.
 */
mxEdgeHandler.prototype.snapToTerminals = false;

/**
 * Variable: handleImage
 *
 * Optional <mxImage> to be used as handles. Default is null.
 */
mxEdgeHandler.prototype.handleImage = null;

/**
 * Variable: tolerance
 *
 * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
 */
mxEdgeHandler.prototype.tolerance = 0;

/**
 * Variable: outlineConnect
 *
 * Specifies if connections to the outline of a highlighted target should be
 * enabled. This will allow to place the connection point along the outline of
 * the highlighted target. Default is false.
 */
mxEdgeHandler.prototype.outlineConnect = false;

/**
 * Variable: manageLabelHandle
 *
 * Specifies if the label handle should be moved if it intersects with another
 * handle. Uses <checkLabelHandle> for checking and moving. Default is false.
 */
mxEdgeHandler.prototype.manageLabelHandle = false;

/**
 * Function: init
 *
 * Initializes the shapes required for this edge handler.
 */
mxEdgeHandler.prototype.init = function () {
  this.graph = this.state.view.graph;
  this.marker = this.createMarker();
  this.constraintHandler = new mxConstraintHandler(this.graph);

  // Clones the original points from the cell
  // and makes sure at least one point exists
  this.points = [];

  // Uses the absolute points of the state
  // for the initial configuration and preview
  this.abspoints = this.getSelectionPoints(this.state);
  this.shape = this.createSelectionShape(this.abspoints);
  this.shape.dialect =
    this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
  this.shape.init(this.graph.getView().getOverlayPane());
  this.shape.pointerEvents = false;
  this.shape.setCursor(mxConstants.CURSOR_MOVABLE_EDGE);
  mxEvent.redirectMouseEvents(this.shape.node, this.graph, this.state);

  // Updates preferHtml
  this.preferHtml = this.state.text != null && this.state.text.node.parentNode == this.graph.container;

  if (!this.preferHtml) {
    // Checks source terminal
    var sourceState = this.state.getVisibleTerminalState(true);

    if (sourceState != null) {
      this.preferHtml = sourceState.text != null && sourceState.text.node.parentNode == this.graph.container;
    }

    if (!this.preferHtml) {
      // Checks target terminal
      var targetState = this.state.getVisibleTerminalState(false);

      if (targetState != null) {
        this.preferHtml = targetState.text != null && targetState.text.node.parentNode == this.graph.container;
      }
    }
  }

  // Creates bends for the non-routed absolute points
  // or bends that don't correspond to points
  if (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells || mxGraphHandler.prototype.maxCells <= 0) {
    this.bends = this.createBends();

    if (this.isVirtualBendsEnabled()) {
      this.virtualBends = this.createVirtualBends();
    }
  }

  // Adds a rectangular handle for the label position
  this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
  this.labelShape = this.createLabelHandleShape();
  this.initBend(this.labelShape);
  this.labelShape.setCursor(mxConstants.CURSOR_LABEL_HANDLE);

  this.customHandles = this.createCustomHandles();

  this.updateParentHighlight();
  this.redraw();
};

/**
 * Function: isParentHighlightVisible
 *
 * Returns true if the parent highlight should be visible. This implementation
 * always returns true.
 */
mxEdgeHandler.prototype.isParentHighlightVisible = mxVertexHandler.prototype.isParentHighlightVisible;

/**
 * Function: updateParentHighlight
 *
 * Updates the highlight of the parent if <parentHighlightEnabled> is true.
 */
mxEdgeHandler.prototype.updateParentHighlight = mxVertexHandler.prototype.updateParentHighlight;

/**
 * Function: createCustomHandles
 *
 * Returns an array of custom handles. This implementation returns null.
 */
mxEdgeHandler.prototype.createCustomHandles = function () {
  return null;
};

/**
 * Function: isVirtualBendsEnabled
 *
 * Returns true if virtual bends should be added. This returns true if
 * <virtualBendsEnabled> is true and the current style allows and
 * renders custom waypoints.
 */
mxEdgeHandler.prototype.isVirtualBendsEnabled = function (evt) {
  return (
    this.virtualBendsEnabled &&
    (this.state.style[mxConstants.STYLE_EDGE] == null ||
      this.state.style[mxConstants.STYLE_EDGE] == mxConstants.NONE ||
      this.state.style[mxConstants.STYLE_NOEDGESTYLE] == 1) &&
    mxUtils.getValue(this.state.style, mxConstants.STYLE_SHAPE, null) != 'arrow'
  );
};

/**
 * Function: isCellEnabled
 *
 * Returns true if the given cell allows new connections to be created. This implementation
 * always returns true.
 */
mxEdgeHandler.prototype.isCellEnabled = function (cell) {
  return true;
};

/**
 * Function: isAddPointEvent
 *
 * Returns true if the given event is a trigger to add a new point. This
 * implementation returns true if shift is pressed.
 */
mxEdgeHandler.prototype.isAddPointEvent = function (evt) {
  return mxEvent.isShiftDown(evt);
};

/**
 * Function: isRemovePointEvent
 *
 * Returns true if the given event is a trigger to remove a point. This
 * implementation returns true if shift is pressed.
 */
mxEdgeHandler.prototype.isRemovePointEvent = function (evt) {
  return mxEvent.isShiftDown(evt);
};

/**
 * Function: getSelectionPoints
 *
 * Returns the list of points that defines the selection stroke.
 */
mxEdgeHandler.prototype.getSelectionPoints = function (state) {
  return state.absolutePoints;
};

/**
 * Function: createParentHighlightShape
 *
 * Creates the shape used to draw the selection border.
 */
mxEdgeHandler.prototype.createParentHighlightShape = function (bounds) {
  var shape = new mxRectangleShape(mxRectangle.fromRectangle(bounds), null, this.getSelectionColor());
  shape.strokewidth = this.getSelectionStrokeWidth();
  shape.isDashed = this.isSelectionDashed();

  return shape;
};

/**
 * Function: createSelectionShape
 *
 * Creates the shape used to draw the selection border.
 */
mxEdgeHandler.prototype.createSelectionShape = function (points) {
  var shape = new this.state.shape.constructor();
  shape.outline = true;
  shape.apply(this.state);

  shape.isDashed = this.isSelectionDashed();
  shape.stroke = this.getSelectionColor();
  shape.isShadow = false;

  return shape;
};

/**
 * Function: getSelectionColor
 *
 * Returns <mxConstants.EDGE_SELECTION_COLOR>.
 */
mxEdgeHandler.prototype.getSelectionColor = function () {
  return mxConstants.EDGE_SELECTION_COLOR;
};

/**
 * Function: getSelectionStrokeWidth
 *
 * Returns <mxConstants.EDGE_SELECTION_STROKEWIDTH>.
 */
mxEdgeHandler.prototype.getSelectionStrokeWidth = function () {
  return mxConstants.EDGE_SELECTION_STROKEWIDTH;
};

/**
 * Function: isSelectionDashed
 *
 * Returns <mxConstants.EDGE_SELECTION_DASHED>.
 */
mxEdgeHandler.prototype.isSelectionDashed = function () {
  return mxConstants.EDGE_SELECTION_DASHED;
};

/**
 * Function: isConnectableCell
 *
 * Returns true if the given cell is connectable. This is a hook to
 * disable floating connections. This implementation returns true.
 */
mxEdgeHandler.prototype.isConnectableCell = function (cell) {
  return true;
};

/**
 * Function: getCellAt
 *
 * Creates and returns the <mxCellMarker> used in <marker>.
 */
mxEdgeHandler.prototype.getCellAt = function (x, y) {
  return !this.outlineConnect ? this.graph.getCellAt(x, y) : null;
};

/**
 * Function: createMarker
 *
 * Creates and returns the <mxCellMarker> used in <marker>.
 */
mxEdgeHandler.prototype.createMarker = function () {
  var marker = new mxCellMarker(this.graph);
  var self = this; // closure

  // Only returns edges if they are connectable and never returns
  // the edge that is currently being modified
  marker.getCell = function (me) {
    var cell = mxCellMarker.prototype.getCell.apply(this, arguments);

    // Checks for cell at preview point (with grid)
    if ((cell == self.state.cell || cell == null) && self.currentPoint != null) {
      cell = self.graph.getCellAt(self.currentPoint.x, self.currentPoint.y);
    }

    // Uses connectable parent vertex if one exists
    if (cell != null && !this.graph.isCellConnectable(cell)) {
      var parent = this.graph.getModel().getParent(cell);

      if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) {
        cell = parent;
      }
    }

    var model = self.graph.getModel();

    if (
      (this.graph.isSwimlane(cell) &&
        self.currentPoint != null &&
        this.graph.hitsSwimlaneContent(cell, self.currentPoint.x, self.currentPoint.y)) ||
      !self.isConnectableCell(cell) ||
      cell == self.state.cell ||
      (cell != null && !self.graph.connectableEdges && model.isEdge(cell)) ||
      model.isAncestor(self.state.cell, cell)
    ) {
      cell = null;
    }

    if (!this.graph.isCellConnectable(cell)) {
      cell = null;
    }

    return cell;
  };

  // Sets the highlight color according to validateConnection
  marker.isValidState = function (state) {
    var model = self.graph.getModel();
    var other = self.graph.view.getTerminalPort(
      state,
      self.graph.view.getState(model.getTerminal(self.state.cell, !self.isSource)),
      !self.isSource
    );
    var otherCell = other != null ? other.cell : null;
    var source = self.isSource ? state.cell : otherCell;
    var target = self.isSource ? otherCell : state.cell;

    // Updates the error message of the handler
    self.error = self.validateConnection(source, target);

    return self.error == null;
  };

  return marker;
};

/**
 * Function: validateConnection
 *
 * Returns the error message or an empty string if the connection for the
 * given source, target pair is not valid. Otherwise it returns null. This
 * implementation uses <mxGraph.getEdgeValidationError>.
 *
 * Parameters:
 *
 * source - <mxCell> that represents the source terminal.
 * target - <mxCell> that represents the target terminal.
 */
mxEdgeHandler.prototype.validateConnection = function (source, target) {
  return this.graph.getEdgeValidationError(this.state.cell, source, target);
};

/**
 * Function: createBends
 *
 * Creates and returns the bends used for modifying the edge. This is
 * typically an array of <mxRectangleShapes>.
 */
mxEdgeHandler.prototype.createBends = function () {
  var cell = this.state.cell;
  var bends = [];

  for (var i = 0; i < this.abspoints.length; i++) {
    if (this.isHandleVisible(i)) {
      var source = i == 0;
      var target = i == this.abspoints.length - 1;
      var terminal = source || target;

      if (terminal || this.graph.isCellBendable(cell)) {
        mxUtils.bind(this, function (index) {
          var bend = this.createHandleShape(index);
          this.initBend(
            bend,
            mxUtils.bind(
              this,
              mxUtils.bind(this, function () {
                if (this.dblClickRemoveEnabled) {
                  this.removePoint(this.state, index);
                }
              })
            )
          );

          if (this.isHandleEnabled(i)) {
            bend.setCursor(terminal ? mxConstants.CURSOR_TERMINAL_HANDLE : mxConstants.CURSOR_BEND_HANDLE);
          }

          bends.push(bend);

          if (!terminal) {
            this.points.push(new mxPoint(0, 0));
            bend.node.style.visibility = 'hidden';
          }
        })(i);
      }
    }
  }

  return bends;
};

/**
 * Function: createVirtualBends
 *
 * Creates and returns the bends used for modifying the edge. This is
 * typically an array of <mxRectangleShapes>.
 */
mxEdgeHandler.prototype.createVirtualBends = function () {
  var cell = this.state.cell;
  var last = this.abspoints[0];
  var bends = [];

  if (this.graph.isCellBendable(cell)) {
    for (var i = 1; i < this.abspoints.length; i++) {
      mxUtils.bind(this, function (bend) {
        this.initBend(bend);
        bend.setCursor(mxConstants.CURSOR_VIRTUAL_BEND_HANDLE);
        bends.push(bend);
      })(this.createHandleShape());
    }
  }

  return bends;
};

/**
 * Function: isHandleEnabled
 *
 * Creates the shape used to display the given bend.
 */
mxEdgeHandler.prototype.isHandleEnabled = function (index) {
  return true;
};

/**
 * Function: isHandleVisible
 *
 * Returns true if the handle at the given index is visible.
 */
mxEdgeHandler.prototype.isHandleVisible = function (index) {
  var source = this.state.getVisibleTerminalState(true);
  var target = this.state.getVisibleTerminalState(false);
  var geo = this.graph.getCellGeometry(this.state.cell);
  var edgeStyle = geo != null ? this.graph.view.getEdgeStyle(this.state, geo.points, source, target) : null;

  return edgeStyle != mxEdgeStyle.EntityRelation || index == 0 || index == this.abspoints.length - 1;
};

/**
 * Function: createHandleShape
 *
 * Creates the shape used to display the given bend. Note that the index may be
 * null for special cases, such as when called from
 * <mxElbowEdgeHandler.createVirtualBend>. Only images and rectangles should be
 * returned if support for HTML labels with not foreign objects is required.
 * Index if null for virtual handles.
 */
mxEdgeHandler.prototype.createHandleShape = function (index) {
  if (this.handleImage != null) {
    var shape = new mxImageShape(
      new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height),
      this.handleImage.src
    );

    // Allows HTML rendering of the images
    shape.preserveImageAspect = false;

    return shape;
  } else {
    var s = mxConstants.HANDLE_SIZE;

    if (this.preferHtml) {
      s -= 1;
    }

    return new mxRectangleShape(
      new mxRectangle(0, 0, s, s),
      mxConstants.HANDLE_FILLCOLOR,
      mxConstants.HANDLE_STROKECOLOR
    );
  }
};

/**
 * Function: createLabelHandleShape
 *
 * Creates the shape used to display the the label handle.
 */
mxEdgeHandler.prototype.createLabelHandleShape = function () {
  if (this.labelHandleImage != null) {
    var shape = new mxImageShape(
      new mxRectangle(0, 0, this.labelHandleImage.width, this.labelHandleImage.height),
      this.labelHandleImage.src
    );

    // Allows HTML rendering of the images
    shape.preserveImageAspect = false;

    return shape;
  } else {
    var s = mxConstants.LABEL_HANDLE_SIZE;
    return new mxRectangleShape(
      new mxRectangle(0, 0, s, s),
      mxConstants.LABEL_HANDLE_FILLCOLOR,
      mxConstants.HANDLE_STROKECOLOR
    );
  }
};

/**
 * Function: initBend
 *
 * Helper method to initialize the given bend.
 *
 * Parameters:
 *
 * bend - <mxShape> that represents the bend to be initialized.
 */
mxEdgeHandler.prototype.initBend = function (bend, dblClick) {
  if (this.preferHtml) {
    bend.dialect = mxConstants.DIALECT_STRICTHTML;
    bend.init(this.graph.container);
  } else {
    bend.dialect =
      this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_MIXEDHTML : mxConstants.DIALECT_SVG;
    bend.init(this.graph.getView().getOverlayPane());
  }

  mxEvent.redirectMouseEvents(bend.node, this.graph, this.state, null, null, null, dblClick);

  // Fixes lost event tracking for images in quirks / IE8 standards
  if (mxClient.IS_QUIRKS || document.documentMode == 8) {
    mxEvent.addListener(bend.node, 'dragstart', function (evt) {
      mxEvent.consume(evt);

      return false;
    });
  }

  if (mxClient.IS_TOUCH) {
    bend.node.setAttribute('pointer-events', 'none');
  }
};

/**
 * Function: getHandleForEvent
 *
 * Returns the index of the handle for the given event.
 */
mxEdgeHandler.prototype.getHandleForEvent = function (me) {
  var result = null;

  if (this.state != null) {
    // Connection highlight may consume events before they reach sizer handle
    var tol = !mxEvent.isMouseEvent(me.getEvent()) ? this.tolerance : 1;
    var hit =
      this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)
        ? new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol)
        : null;
    var minDistSq = null;

    function checkShape(shape) {
      if (
        shape != null &&
        shape.node != null &&
        shape.node.style.display != 'none' &&
        shape.node.style.visibility != 'hidden' &&
        (me.isSource(shape) || (hit != null && mxUtils.intersects(shape.bounds, hit)))
      ) {
        var dx = me.getGraphX() - shape.bounds.getCenterX();
        var dy = me.getGraphY() - shape.bounds.getCenterY();
        var tmp = dx * dx + dy * dy;

        if (minDistSq == null || tmp <= minDistSq) {
          minDistSq = tmp;

          return true;
        }
      }

      return false;
    }

    if (this.customHandles != null && this.isCustomHandleEvent(me)) {
      // Inverse loop order to match display order
      for (var i = this.customHandles.length - 1; i >= 0; i--) {
        if (checkShape(this.customHandles[i].shape)) {
          // LATER: Return reference to active shape
          return mxEvent.CUSTOM_HANDLE - i;
        }
      }
    }

    if (me.isSource(this.state.text) || checkShape(this.labelShape)) {
      result = mxEvent.LABEL_HANDLE;
    }

    if (this.bends != null) {
      for (var i = 0; i < this.bends.length; i++) {
        if (checkShape(this.bends[i])) {
          result = i;
        }
      }
    }

    if (this.virtualBends != null && this.isAddVirtualBendEvent(me)) {
      for (var i = 0; i < this.virtualBends.length; i++) {
        if (checkShape(this.virtualBends[i])) {
          result = mxEvent.VIRTUAL_HANDLE - i;
        }
      }
    }
  }

  return result;
};

/**
 * Function: isAddVirtualBendEvent
 *
 * Returns true if the given event allows virtual bends to be added. This
 * implementation returns true.
 */
mxEdgeHandler.prototype.isAddVirtualBendEvent = function (me) {
  return true;
};

/**
 * Function: isCustomHandleEvent
 *
 * Returns true if the given event allows custom handles to be changed. This
 * implementation returns true.
 */
mxEdgeHandler.prototype.isCustomHandleEvent = function (me) {
  return true;
};

/**
 * Function: mouseDown
 *
 * Handles the event by checking if a special element of the handler
 * was clicked, in which case the index parameter is non-null. The
 * indices may be one of <LABEL_HANDLE> or the number of the respective
 * control point. The source and target points are used for reconnecting
 * the edge.
 */
mxEdgeHandler.prototype.mouseDown = function (sender, me) {
  var handle = this.getHandleForEvent(me);

  if (this.bends != null && this.bends[handle] != null) {
    var b = this.bends[handle].bounds;
    this.snapPoint = new mxPoint(b.getCenterX(), b.getCenterY());
  }

  if (this.addEnabled && handle == null && this.isAddPointEvent(me.getEvent())) {
    this.addPoint(this.state, me.getEvent());
    me.consume();
  } else if (handle != null && !me.isConsumed() && this.graph.isEnabled()) {
    if (this.removeEnabled && this.isRemovePointEvent(me.getEvent())) {
      this.removePoint(this.state, handle);
    } else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell())) {
      if (handle <= mxEvent.VIRTUAL_HANDLE) {
        mxUtils.setOpacity(this.virtualBends[mxEvent.VIRTUAL_HANDLE - handle].node, 100);
      }

      this.start(me.getX(), me.getY(), handle);
    }

    me.consume();
  }
};

/**
 * Function: start
 *
 * Starts the handling of the mouse gesture.
 */
mxEdgeHandler.prototype.start = function (x, y, index) {
  this.startX = x;
  this.startY = y;

  this.isSource = this.bends == null ? false : index == 0;
  this.isTarget = this.bends == null ? false : index == this.bends.length - 1;
  this.isLabel = index == mxEvent.LABEL_HANDLE;

  if (this.isSource || this.isTarget) {
    var cell = this.state.cell;
    var terminal = this.graph.model.getTerminal(cell, this.isSource);

    if (
      (terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) ||
      (terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource))
    ) {
      this.index = index;
    }
  } else {
    this.index = index;
  }

  // Hides other custom handles
  if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) {
    if (this.customHandles != null) {
      for (var i = 0; i < this.customHandles.length; i++) {
        if (i != mxEvent.CUSTOM_HANDLE - this.index) {
          this.customHandles[i].setVisible(false);
        }
      }
    }
  }
};

/**
 * Function: clonePreviewState
 *
 * Returns a clone of the current preview state for the given point and terminal.
 */
mxEdgeHandler.prototype.clonePreviewState = function (point, terminal) {
  return this.state.clone();
};

/**
 * Function: getSnapToTerminalTolerance
 *
 * Returns the tolerance for the guides. Default value is
 * gridSize * scale / 2.
 */
mxEdgeHandler.prototype.getSnapToTerminalTolerance = function () {
  return (this.graph.gridSize * this.graph.view.scale) / 2;
};

/**
 * Function: updateHint
 *
 * Hook for subclassers do show details while the handler is active.
 */
mxEdgeHandler.prototype.updateHint = function (me, point) {};

/**
 * Function: removeHint
 *
 * Hooks for subclassers to hide details when the handler gets inactive.
 */
mxEdgeHandler.prototype.removeHint = function () {};

/**
 * Function: roundLength
 *
 * Hook for rounding the unscaled width or height. This uses Math.round.
 */
mxEdgeHandler.prototype.roundLength = function (length) {
  return Math.round(length);
};

/**
 * Function: isSnapToTerminalsEvent
 *
 * Returns true if <snapToTerminals> is true and if alt is not pressed.
 */
mxEdgeHandler.prototype.isSnapToTerminalsEvent = function (me) {
  return this.snapToTerminals && !mxEvent.isAltDown(me.getEvent());
};

/**
 * Function: getPointForEvent
 *
 * Returns the point for the given event.
 */
mxEdgeHandler.prototype.getPointForEvent = function (me) {
  var view = this.graph.getView();
  var scale = view.scale;
  var point = new mxPoint(
    this.roundLength(me.getGraphX() / scale) * scale,
    this.roundLength(me.getGraphY() / scale) * scale
  );

  var tt = this.getSnapToTerminalTolerance();
  var overrideX = false;
  var overrideY = false;

  if (tt > 0 && this.isSnapToTerminalsEvent(me)) {
    function snapToPoint(pt) {
      if (pt != null) {
        var x = pt.x;

        if (Math.abs(point.x - x) < tt) {
          point.x = x;
          overrideX = true;
        }

        var y = pt.y;

        if (Math.abs(point.y - y) < tt) {
          point.y = y;
          overrideY = true;
        }
      }
    }

    // Temporary function
    function snapToTerminal(terminal) {
      if (terminal != null) {
        snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal), view.getRoutingCenterY(terminal)));
      }
    }

    snapToTerminal.call(this, this.state.getVisibleTerminalState(true));
    snapToTerminal.call(this, this.state.getVisibleTerminalState(false));

    if (this.state.absolutePoints != null) {
      for (var i = 0; i < this.state.absolutePoints.length; i++) {
        snapToPoint.call(this, this.state.absolutePoints[i]);
      }
    }
  }

  if (this.graph.isGridEnabledEvent(me.getEvent())) {
    var tr = view.translate;

    if (!overrideX) {
      point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
    }

    if (!overrideY) {
      point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
    }
  }

  return point;
};

/**
 * Function: getPreviewTerminalState
 *
 * Updates the given preview state taking into account the state of the constraint handler.
 */
mxEdgeHandler.prototype.getPreviewTerminalState = function (me) {
  this.constraintHandler.update(
    me,
    this.isSource,
    true,
    me.isSource(this.marker.highlight.shape) ? null : this.currentPoint
  );

  if (this.constraintHandler.currentFocus != null && this.constraintHandler.currentConstraint != null) {
    // Handles special case where grid is large and connection point is at actual point in which
    // case the outline is not followed as long as we're < gridSize / 2 away from that point
    if (
      this.marker.highlight != null &&
      this.marker.highlight.state != null &&
      this.marker.highlight.state.cell == this.constraintHandler.currentFocus.cell
    ) {
      // Direct repaint needed if cell already highlighted
      if (this.marker.highlight.shape.stroke != 'transparent') {
        this.marker.highlight.shape.stroke = 'transparent';
        this.marker.highlight.repaint();
      }
    } else {
      this.marker.markCell(this.constraintHandler.currentFocus.cell, 'transparent');
    }

    var model = this.graph.getModel();
    var other = this.graph.view.getTerminalPort(
      this.state,
      this.graph.view.getState(model.getTerminal(this.state.cell, !this.isSource)),
      !this.isSource
    );
    var otherCell = other != null ? other.cell : null;
    var source = this.isSource ? this.constraintHandler.currentFocus.cell : otherCell;
    var target = this.isSource ? otherCell : this.constraintHandler.currentFocus.cell;

    // Updates the error message of the handler
    this.error = this.validateConnection(source, target);
    var result = null;

    if (this.error == null) {
      result = this.constraintHandler.currentFocus;
    }

    if (this.error != null || (result != null && !this.isCellEnabled(result.cell))) {
      this.constraintHandler.reset();
    }

    return result;
  } else if (!this.graph.isIgnoreTerminalEvent(me.getEvent())) {
    this.marker.process(me);
    var state = this.marker.getValidState();

    if (state != null && !this.isCellEnabled(state.cell)) {
      this.constraintHandler.reset();
      this.marker.reset();
    }

    return this.marker.getValidState();
  } else {
    this.marker.reset();

    return null;
  }
};

/**
 * Function: getPreviewPoints
 *
 * Updates the given preview state taking into account the state of the constraint handler.
 *
 * Parameters:
 *
 * pt - <mxPoint> that contains the current pointer position.
 * me - Optional <mxMouseEvent> that contains the current event.
 */
mxEdgeHandler.prototype.getPreviewPoints = function (pt, me) {
  var geometry = this.graph.getCellGeometry(this.state.cell);
  var points = geometry.points != null ? geometry.points.slice() : null;
  var point = new mxPoint(pt.x, pt.y);
  var result = null;

  if (!this.isSource && !this.isTarget) {
    this.convertPoint(point, false);

    if (points == null) {
      points = [point];
    } else {
      // Adds point from virtual bend
      if (this.index <= mxEvent.VIRTUAL_HANDLE) {
        points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 0, point);
      }

      // Removes point if dragged on terminal point
      if (!this.isSource && !this.isTarget) {
        for (var i = 0; i < this.bends.length; i++) {
          if (i != this.index) {
            var bend = this.bends[i];

            if (bend != null && mxUtils.contains(bend.bounds, pt.x, pt.y)) {
              if (this.index <= mxEvent.VIRTUAL_HANDLE) {
                points.splice(mxEvent.VIRTUAL_HANDLE - this.index, 1);
              } else {
                points.splice(this.index - 1, 1);
              }

              result = points;
            }
          }
        }

        // Removes point if user tries to straighten a segment
        if (result == null && this.straightRemoveEnabled && (me == null || !mxEvent.isAltDown(me.getEvent()))) {
          var tol = this.graph.tolerance * this.graph.tolerance;
          var abs = this.state.absolutePoints.slice();
          abs[this.index] = pt;

          // Handes special case where removing waypoint affects tolerance (flickering)
          var src = this.state.getVisibleTerminalState(true);

          if (src != null) {
            var c = this.graph.getConnectionConstraint(this.state, src, true);

            // Checks if point is not fixed
            if (c == null || this.graph.getConnectionPoint(src, c) == null) {
              abs[0] = new mxPoint(src.view.getRoutingCenterX(src), src.view.getRoutingCenterY(src));
            }
          }

          var trg = this.state.getVisibleTerminalState(false);

          if (trg != null) {
            var c = this.graph.getConnectionConstraint(this.state, trg, false);

            // Checks if point is not fixed
            if (c == null || this.graph.getConnectionPoint(trg, c) == null) {
              abs[abs.length - 1] = new mxPoint(trg.view.getRoutingCenterX(trg), trg.view.getRoutingCenterY(trg));
            }
          }

          function checkRemove(idx, tmp) {
            if (
              idx > 0 &&
              idx < abs.length - 1 &&
              mxUtils.ptSegDistSq(abs[idx - 1].x, abs[idx - 1].y, abs[idx + 1].x, abs[idx + 1].y, tmp.x, tmp.y) < tol
            ) {
              points.splice(idx - 1, 1);
              result = points;
            }
          }

          // LATER: Check if other points can be removed if a segment is made straight
          checkRemove(this.index, pt);
        }
      }

      // Updates existing point
      if (result == null && this.index > mxEvent.VIRTUAL_HANDLE) {
        points[this.index - 1] = point;
      }
    }
  } else if (this.graph.resetEdgesOnConnect) {
    points = null;
  }

  return result != null ? result : points;
};

/**
 * Function: isOutlineConnectEvent
 *
 * Returns true if <outlineConnect> is true and the source of the event is the outline shape
 * or shift is pressed.
 */
mxEdgeHandler.prototype.isOutlineConnectEvent = function (me) {
  var offset = mxUtils.getOffset(this.graph.container);
  var evt = me.getEvent();

  var clientX = mxEvent.getClientX(evt);
  var clientY = mxEvent.getClientY(evt);

  var doc = document.documentElement;
  var left = (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0);
  var top = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);

  var gridX = this.currentPoint.x - this.graph.container.scrollLeft + offset.x - left;
  var gridY = this.currentPoint.y - this.graph.container.scrollTop + offset.y - top;

  return (
    this.outlineConnect &&
    !mxEvent.isShiftDown(me.getEvent()) &&
    (me.isSource(this.marker.highlight.shape) ||
      (mxEvent.isAltDown(me.getEvent()) && me.getState() != null) ||
      this.marker.highlight.isHighlightAt(clientX, clientY) ||
      ((gridX != clientX || gridY != clientY) &&
        me.getState() == null &&
        this.marker.highlight.isHighlightAt(gridX, gridY)))
  );
};

/**
 * Function: updatePreviewState
 *
 * Updates the given preview state taking into account the state of the constraint handler.
 */
mxEdgeHandler.prototype.updatePreviewState = function (edge, point, terminalState, me, outline) {
  // Computes the points for the edge style and terminals
  var sourceState = this.isSource ? terminalState : this.state.getVisibleTerminalState(true);
  var targetState = this.isTarget ? terminalState : this.state.getVisibleTerminalState(false);

  var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true);
  var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false);

  var constraint = this.constraintHandler.currentConstraint;

  if (constraint == null && outline) {
    if (terminalState != null) {
      // Handles special case where mouse is on outline away from actual end point
      // in which case the grid is ignored and mouse point is used instead
      if (me.isSource(this.marker.highlight.shape)) {
        point = new mxPoint(me.getGraphX(), me.getGraphY());
      }

      constraint = this.graph.getOutlineConstraint(point, terminalState, me);
      this.constraintHandler.setFocus(me, terminalState, this.isSource);
      this.constraintHandler.currentConstraint = constraint;
      this.constraintHandler.currentPoint = point;
    } else {
      constraint = new mxConnectionConstraint();
    }
  }

  if (this.outlineConnect && this.marker.highlight != null && this.marker.highlight.shape != null) {
    var s = this.graph.view.scale;

    if (this.constraintHandler.currentConstraint != null && this.constraintHandler.currentFocus != null) {
      this.marker.highlight.shape.stroke = outline ? mxConstants.OUTLINE_HIGHLIGHT_COLOR : 'transparent';
      this.marker.highlight.shape.strokewidth = mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH / s / s;
      this.marker.highlight.repaint();
    } else if (this.marker.hasValidState()) {
      this.marker.highlight.shape.stroke =
        this.graph.isCellConnectable(me.getCell()) && this.marker.getValidState() != me.getState()
          ? 'transparent'
          : mxConstants.DEFAULT_VALID_COLOR;
      this.marker.highlight.shape.strokewidth = mxConstants.HIGHLIGHT_STROKEWIDTH / s / s;
      this.marker.highlight.repaint();
    }
  }

  if (this.isSource) {
    sourceConstraint = constraint;
  } else if (this.isTarget) {
    targetConstraint = constraint;
  }

  if (this.isSource || this.isTarget) {
    if (constraint != null && constraint.point != null) {
      edge.style[this.isSource ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X] = constraint.point.x;
      edge.style[this.isSource ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y] = constraint.point.y;
    } else {
      delete edge.style[this.isSource ? mxConstants.STYLE_EXIT_X : mxConstants.STYLE_ENTRY_X];
      delete edge.style[this.isSource ? mxConstants.STYLE_EXIT_Y : mxConstants.STYLE_ENTRY_Y];
    }
  }

  edge.setVisibleTerminalState(sourceState, true);
  edge.setVisibleTerminalState(targetState, false);

  if (!this.isSource || sourceState != null) {
    edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint);
  }

  if (!this.isTarget || targetState != null) {
    edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint);
  }

  if ((this.isSource || this.isTarget) && terminalState == null) {
    edge.setAbsoluteTerminalPoint(point, this.isSource);

    if (this.marker.getMarkedState() == null) {
      this.error = this.graph.allowDanglingEdges ? null : '';
    }
  }

  edge.view.updatePoints(edge, this.points, sourceState, targetState);
  edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState);
};

/**
 * Function: mouseMove
 *
 * Handles the event by updating the preview.
 */
mxEdgeHandler.prototype.mouseMove = function (sender, me) {
  if (this.index != null && this.marker != null) {
    this.currentPoint = this.getPointForEvent(me);
    this.error = null;

    // Uses the current point from the constraint handler if available
    if (
      !this.graph.isIgnoreTerminalEvent(me.getEvent()) &&
      mxEvent.isShiftDown(me.getEvent()) &&
      this.snapPoint != null
    ) {
      if (Math.abs(this.snapPoint.x - this.currentPoint.x) < Math.abs(this.snapPoint.y - this.currentPoint.y)) {
        this.currentPoint.x = this.snapPoint.x;
      } else {
        this.currentPoint.y = this.snapPoint.y;
      }
    }

    if (this.index <= mxEvent.CUSTOM_HANDLE && this.index > mxEvent.VIRTUAL_HANDLE) {
      if (this.customHandles != null) {
        this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].processEvent(me);
        this.customHandles[mxEvent.CUSTOM_HANDLE - this.index].positionChanged();

        if (this.shape != null && this.shape.node != null) {
          this.shape.node.style.display = 'none';
        }
      }
    } else if (this.isLabel) {
      this.label.x = this.currentPoint.x;
      this.label.y = this.currentPoint.y;
    } else {
      this.points = this.getPreviewPoints(this.currentPoint, me);
      var terminalState = this.isSource || this.isTarget ? this.getPreviewTerminalState(me) : null;

      if (
        this.constraintHandler.currentConstraint != null &&
        this.constraintHandler.currentFocus != null &&
        this.constraintHandler.currentPoint != null
      ) {
        this.currentPoint = this.constraintHandler.currentPoint.clone();
      } else if (this.outlineConnect) {
        // Need to check outline before cloning terminal state
        var outline = this.isSource || this.isTarget ? this.isOutlineConnectEvent(me) : false;

        if (outline) {
          terminalState = this.marker.highlight.state;
        } else if (
          terminalState != null &&
          terminalState != me.getState() &&
          this.graph.isCellConnectable(me.getCell()) &&
          this.marker.highlight.shape != null
        ) {
          this.marker.highlight.shape.stroke = 'transparent';
          this.marker.highlight.repaint();
          terminalState = null;
        }
      }

      if (terminalState != null && !this.isCellEnabled(terminalState.cell)) {
        terminalState = null;
        this.marker.reset();
      }

      var clone = this.clonePreviewState(this.currentPoint, terminalState != null ? terminalState.cell : null);
      this.updatePreviewState(clone, this.currentPoint, terminalState, me, outline);

      // Sets the color of the preview to valid or invalid, updates the
      // points of the preview and redraws
      var color = this.error == null ? this.marker.validColor : this.marker.invalidColor;
      this.setPreviewColor(color);
      this.abspoints = clone.absolutePoints;
      this.active = true;
      this.updateHint(me, this.currentPoint);
    }

    // This should go before calling isOutlineConnectEvent above. As a workaround
    // we add an offset of gridSize to the hint to avoid problem with hit detection
    // in highlight.isHighlightAt (which uses comonentFromPoint)
    this.drawPreview();
    mxEvent.consume(me.getEvent());
    me.consume();
  }
  // Workaround for disabling the connect highlight when over handle
  else if (mxClient.IS_IE && this.getHandleForEvent(me) != null) {
    me.consume(false);
  }
};

/**
 * Function: mouseUp
 *
 * Handles the event to applying the previewed changes on the edge by
 * using <moveLabel>, <connect> or <changePoints>.
 */
mxEdgeHandler.prototype.mouseUp = function (sender, me) {
  // Workaround for wrong event source in Webkit
  if (this.index != null && this.marker != null) {
    if (this.shape != null && this.shape.node != null) {
      this.shape.node.style.display = '';
    }

    var edge = this.state.cell;
    var index = this.index;
    this.index = null;

    // Ignores event if mouse has not been moved
    if (me.getX() != this.startX || me.getY() != this.startY) {
      var clone =
        !this.graph.isIgnoreTerminalEvent(me.getEvent()) &&
        this.graph.isCloneEvent(me.getEvent()) &&
        this.cloneEnabled &&
        this.graph.isCellsCloneable();

      // Displays the reason for not carriying out the change
      // if there is an error message with non-zero length
      if (this.error != null) {
        if (this.error.length > 0) {
          this.graph.validationAlert(this.error);
        }
      } else if (index <= mxEvent.CUSTOM_HANDLE && index > mxEvent.VIRTUAL_HANDLE) {
        if (this.customHandles != null) {
          var model = this.graph.getModel();

          model.beginUpdate();
          try {
            this.customHandles[mxEvent.CUSTOM_HANDLE - index].execute(me);

            if (this.shape != null && this.shape.node != null) {
              this.shape.apply(this.state);
              this.shape.redraw();
            }
          } finally {
            model.endUpdate();
          }
        }
      } else if (this.isLabel) {
        this.moveLabel(this.state, this.label.x, this.label.y);
      } else if (this.isSource || this.isTarget) {
        var terminal = null;

        if (this.constraintHandler.currentConstraint != null && this.constraintHandler.currentFocus != null) {
          terminal = this.constraintHandler.currentFocus.cell;
        }

        if (
          terminal == null &&
          this.marker.hasValidState() &&
          this.marker.highlight != null &&
          this.marker.highlight.shape != null &&
          this.marker.highlight.shape.stroke != 'transparent' &&
          this.marker.highlight.shape.stroke != 'white'
        ) {
          terminal = this.marker.validState.cell;
        }

        if (terminal != null) {
          var model = this.graph.getModel();
          var parent = model.getParent(edge);

          model.beginUpdate();
          try {
            // Clones and adds the cell
            if (clone) {
              var geo = model.getGeometry(edge);
              var clone = this.graph.cloneCell(edge);
              model.add(parent, clone, model.getChildCount(parent));

              if (geo != null) {
                geo = geo.clone();
                model.setGeometry(clone, geo);
              }

              var other = model.getTerminal(edge, !this.isSource);
              this.graph.connectCell(clone, other, !this.isSource);

              edge = clone;
            }

            edge = this.connect(edge, terminal, this.isSource, clone, me);
          } finally {
            model.endUpdate();
          }
        } else if (this.graph.isAllowDanglingEdges()) {
          var pt = this.abspoints[this.isSource ? 0 : this.abspoints.length - 1];
          pt.x = this.roundLength(pt.x / this.graph.view.scale - this.graph.view.translate.x);
          pt.y = this.roundLength(pt.y / this.graph.view.scale - this.graph.view.translate.y);

          var pstate = this.graph.getView().getState(this.graph.getModel().getParent(edge));

          if (pstate != null) {
            pt.x -= pstate.origin.x;
            pt.y -= pstate.origin.y;
          }

          pt.x -= this.graph.panDx / this.graph.view.scale;
          pt.y -= this.graph.panDy / this.graph.view.scale;

          // Destroys and recreates this handler
          edge = this.changeTerminalPoint(edge, pt, this.isSource, clone);
        }
      } else if (this.active) {
        edge = this.changePoints(edge, this.points, clone);
      } else {
        this.graph.getView().invalidate(this.state.cell);
        this.graph.getView().validate(this.state.cell);
      }
    } else if (this.graph.isToggleEvent(me.getEvent())) {
      this.graph.selectCellForEvent(this.state.cell, me.getEvent());
    }

    // Resets the preview color the state of the handler if this
    // handler has not been recreated
    if (this.marker != null) {
      this.reset();

      // Updates the selection if the edge has been cloned
      if (edge != this.state.cell) {
        this.graph.setSelectionCell(edge);
      }
    }

    me.consume();
  }
};

/**
 * Function: reset
 *
 * Resets the state of this handler.
 */
mxEdgeHandler.prototype.reset = function () {
  if (this.active) {
    this.refresh();
  }

  this.error = null;
  this.index = null;
  this.label = null;
  this.points = null;
  this.snapPoint = null;
  this.isLabel = false;
  this.isSource = false;
  this.isTarget = false;
  this.active = false;

  if (this.livePreview && this.sizers != null) {
    for (var i = 0; i < this.sizers.length; i++) {
      if (this.sizers[i] != null) {
        this.sizers[i].node.style.display = '';
      }
    }
  }

  if (this.marker != null) {
    this.marker.reset();
  }

  if (this.constraintHandler != null) {
    this.constraintHandler.reset();
  }

  if (this.customHandles != null) {
    for (var i = 0; i < this.customHandles.length; i++) {
      this.customHandles[i].reset();
    }
  }

  this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);
  this.removeHint();
  this.redraw();
};

/**
 * Function: setPreviewColor
 *
 * Sets the color of the preview to the given value.
 */
mxEdgeHandler.prototype.setPreviewColor = function (color) {
  if (this.shape != null) {
    this.shape.stroke = color;
  }
};

/**
 * Function: convertPoint
 *
 * Converts the given point in-place from screen to unscaled, untranslated
 * graph coordinates and applies the grid. Returns the given, modified
 * point instance.
 *
 * Parameters:
 *
 * point - <mxPoint> to be converted.
 * gridEnabled - Boolean that specifies if the grid should be applied.
 */
mxEdgeHandler.prototype.convertPoint = function (point, gridEnabled) {
  var scale = this.graph.getView().getScale();
  var tr = this.graph.getView().getTranslate();

  if (gridEnabled) {
    point.x = this.graph.snap(point.x);
    point.y = this.graph.snap(point.y);
  }

  point.x = Math.round(point.x / scale - tr.x);
  point.y = Math.round(point.y / scale - tr.y);

  var pstate = this.graph.getView().getState(this.graph.getModel().getParent(this.state.cell));

  if (pstate != null) {
    point.x -= pstate.origin.x;
    point.y -= pstate.origin.y;
  }

  return point;
};

/**
 * Function: moveLabel
 *
 * Changes the coordinates for the label of the given edge.
 *
 * Parameters:
 *
 * edge - <mxCell> that represents the edge.
 * x - Integer that specifies the x-coordinate of the new location.
 * y - Integer that specifies the y-coordinate of the new location.
 */
mxEdgeHandler.prototype.moveLabel = function (edgeState, x, y) {
  var model = this.graph.getModel();
  var geometry = model.getGeometry(edgeState.cell);

  if (geometry != null) {
    var scale = this.graph.getView().scale;
    geometry = geometry.clone();

    if (geometry.relative) {
      // Resets the relative location stored inside the geometry
      var pt = this.graph.getView().getRelativePoint(edgeState, x, y);
      geometry.x = Math.round(pt.x * 10000) / 10000;
      geometry.y = Math.round(pt.y);

      // Resets the offset inside the geometry to find the offset
      // from the resulting point
      geometry.offset = new mxPoint(0, 0);
      var pt = this.graph.view.getPoint(edgeState, geometry);
      geometry.offset = new mxPoint(Math.round((x - pt.x) / scale), Math.round((y - pt.y) / scale));
    } else {
      var points = edgeState.absolutePoints;
      var p0 = points[0];
      var pe = points[points.length - 1];

      if (p0 != null && pe != null) {
        var cx = p0.x + (pe.x - p0.x) / 2;
        var cy = p0.y + (pe.y - p0.y) / 2;

        geometry.offset = new mxPoint(Math.round((x - cx) / scale), Math.round((y - cy) / scale));
        geometry.x = 0;
        geometry.y = 0;
      }
    }

    model.setGeometry(edgeState.cell, geometry);
  }
};

/**
 * Function: connect
 *
 * Changes the terminal or terminal point of the given edge in the graph
 * model.
 *
 * Parameters:
 *
 * edge - <mxCell> that represents the edge to be reconnected.
 * terminal - <mxCell> that represents the new terminal.
 * isSource - Boolean indicating if the new terminal is the source or
 * target terminal.
 * isClone - Boolean indicating if the new connection should be a clone of
 * the old edge.
 * me - <mxMouseEvent> that contains the mouse up event.
 */
mxEdgeHandler.prototype.connect = function (edge, terminal, isSource, isClone, me) {
  var model = this.graph.getModel();
  var parent = model.getParent(edge);

  model.beginUpdate();
  try {
    var constraint = this.constraintHandler.currentConstraint;

    if (constraint == null) {
      constraint = new mxConnectionConstraint();
    }

    this.graph.connectCell(edge, terminal, isSource, constraint);
  } finally {
    model.endUpdate();
  }

  return edge;
};

/**
 * Function: changeTerminalPoint
 *
 * Changes the terminal point of the given edge.
 */
mxEdgeHandler.prototype.changeTerminalPoint = function (edge, point, isSource, clone) {
  var model = this.graph.getModel();

  model.beginUpdate();
  try {
    if (clone) {
      var parent = model.getParent(edge);
      var terminal = model.getTerminal(edge, !isSource);
      edge = this.graph.cloneCell(edge);
      model.add(parent, edge, model.getChildCount(parent));
      model.setTerminal(edge, terminal, !isSource);
    }

    var geo = model.getGeometry(edge);

    if (geo != null) {
      geo = geo.clone();
      geo.setTerminalPoint(point, isSource);
      model.setGeometry(edge, geo);
      this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint());
    }
  } finally {
    model.endUpdate();
  }

  return edge;
};

/**
 * Function: changePoints
 *
 * Changes the control points of the given edge in the graph model.
 */
mxEdgeHandler.prototype.changePoints = function (edge, points, clone) {
  var model = this.graph.getModel();
  model.beginUpdate();
  try {
    if (clone) {
      var parent = model.getParent(edge);
      var source = model.getTerminal(edge, true);
      var target = model.getTerminal(edge, false);
      edge = this.graph.cloneCell(edge);
      model.add(parent, edge, model.getChildCount(parent));
      model.setTerminal(edge, source, true);
      model.setTerminal(edge, target, false);
    }

    var geo = model.getGeometry(edge);

    if (geo != null) {
      geo = geo.clone();
      geo.points = points;

      model.setGeometry(edge, geo);
    }
  } finally {
    model.endUpdate();
  }

  return edge;
};

/**
 * Function: addPoint
 *
 * Adds a control point for the given state and event.
 */
mxEdgeHandler.prototype.addPoint = function (state, evt) {
  var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  var gridEnabled = this.graph.isGridEnabledEvent(evt);
  this.convertPoint(pt, gridEnabled);
  this.addPointAt(state, pt.x, pt.y);
  mxEvent.consume(evt);
};

/**
 * Function: addPointAt
 *
 * Adds a control point at the given point.
 */
mxEdgeHandler.prototype.addPointAt = function (state, x, y) {
  var geo = this.graph.getCellGeometry(state.cell);
  var pt = new mxPoint(x, y);

  if (geo != null) {
    geo = geo.clone();
    var t = this.graph.view.translate;
    var s = this.graph.view.scale;
    var offset = new mxPoint(t.x * s, t.y * s);

    var parent = this.graph.model.getParent(this.state.cell);

    if (this.graph.model.isVertex(parent)) {
      var pState = this.graph.view.getState(parent);
      offset = new mxPoint(pState.x, pState.y);
    }

    var index = mxUtils.findNearestSegment(state, pt.x * s + offset.x, pt.y * s + offset.y);

    if (geo.points == null) {
      geo.points = [pt];
    } else {
      geo.points.splice(index, 0, pt);
    }

    this.graph.getModel().setGeometry(state.cell, geo);
    this.refresh();
    this.redraw();
  }
};

/**
 * Function: removePoint
 *
 * Removes the control point at the given index from the given state.
 */
mxEdgeHandler.prototype.removePoint = function (state, index) {
  if (index > 0 && index < this.abspoints.length - 1) {
    var geo = this.graph.getCellGeometry(this.state.cell);

    if (geo != null && geo.points != null) {
      geo = geo.clone();
      geo.points.splice(index - 1, 1);
      this.graph.getModel().setGeometry(state.cell, geo);
      this.refresh();
      this.redraw();
    }
  }
};

/**
 * Function: getHandleFillColor
 *
 * Returns the fillcolor for the handle at the given index.
 */
mxEdgeHandler.prototype.getHandleFillColor = function (index) {
  var isSource = index == 0;
  var cell = this.state.cell;
  var terminal = this.graph.getModel().getTerminal(cell, isSource);
  var color = mxConstants.HANDLE_FILLCOLOR;

  if (
    (terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) ||
    (terminal == null && !this.graph.isTerminalPointMovable(cell, isSource))
  ) {
    color = mxConstants.LOCKED_HANDLE_FILLCOLOR;
  } else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource)) {
    color = mxConstants.CONNECT_HANDLE_FILLCOLOR;
  }

  return color;
};

/**
 * Function: redraw
 *
 * Redraws the preview, and the bends- and label control points.
 */
mxEdgeHandler.prototype.redraw = function (ignoreHandles) {
  if (this.state != null) {
    this.abspoints = this.state.absolutePoints.slice();
    var g = this.graph.getModel().getGeometry(this.state.cell);

    if (g != null) {
      var pts = g.points;

      if (this.bends != null && this.bends.length > 0) {
        if (pts != null) {
          if (this.points == null) {
            this.points = [];
          }

          for (var i = 1; i < this.bends.length - 1; i++) {
            if (this.bends[i] != null && this.abspoints[i] != null) {
              this.points[i - 1] = pts[i - 1];
            }
          }
        }
      }
    }

    this.drawPreview();

    if (!ignoreHandles) {
      this.redrawHandles();
    }
  }
};

/**
 * Function: redrawHandles
 *
 * Redraws the handles.
 */
mxEdgeHandler.prototype.redrawHandles = function () {
  var cell = this.state.cell;

  // Updates the handle for the label position
  var b = this.labelShape.bounds;
  this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
  this.labelShape.bounds = new mxRectangle(
    Math.round(this.label.x - b.width / 2),
    Math.round(this.label.y - b.height / 2),
    b.width,
    b.height
  );

  // Shows or hides the label handle depending on the label
  var lab = this.graph.getLabel(cell);
  this.labelShape.visible = lab != null && lab.length > 0 && this.graph.isLabelMovable(cell);

  if (this.bends != null && this.bends.length > 0) {
    var n = this.abspoints.length - 1;

    var p0 = this.abspoints[0];
    var x0 = p0.x;
    var y0 = p0.y;

    b = this.bends[0].bounds;
    this.bends[0].bounds = new mxRectangle(
      Math.floor(x0 - b.width / 2),
      Math.floor(y0 - b.height / 2),
      b.width,
      b.height
    );
    this.bends[0].fill = this.getHandleFillColor(0);
    this.bends[0].redraw();

    if (this.manageLabelHandle) {
      this.checkLabelHandle(this.bends[0].bounds);
    }

    var pe = this.abspoints[n];
    var xn = pe.x;
    var yn = pe.y;

    var bn = this.bends.length - 1;
    b = this.bends[bn].bounds;
    this.bends[bn].bounds = new mxRectangle(
      Math.floor(xn - b.width / 2),
      Math.floor(yn - b.height / 2),
      b.width,
      b.height
    );
    this.bends[bn].fill = this.getHandleFillColor(bn);
    this.bends[bn].redraw();

    if (this.manageLabelHandle) {
      this.checkLabelHandle(this.bends[bn].bounds);
    }

    this.redrawInnerBends(p0, pe);
  }

  if (this.abspoints != null && this.virtualBends != null && this.virtualBends.length > 0) {
    var last = this.abspoints[0];

    for (var i = 0; i < this.virtualBends.length; i++) {
      if (this.virtualBends[i] != null && this.abspoints[i + 1] != null) {
        var pt = this.abspoints[i + 1];
        var b = this.virtualBends[i];
        var x = last.x + (pt.x - last.x) / 2;
        var y = last.y + (pt.y - last.y) / 2;
        b.bounds = new mxRectangle(
          Math.floor(x - b.bounds.width / 2),
          Math.floor(y - b.bounds.height / 2),
          b.bounds.width,
          b.bounds.height
        );
        b.redraw();
        mxUtils.setOpacity(b.node, this.virtualBendOpacity);
        last = pt;

        if (this.manageLabelHandle) {
          this.checkLabelHandle(b.bounds);
        }
      }
    }
  }

  if (this.labelShape != null) {
    this.labelShape.redraw();
  }

  if (this.customHandles != null) {
    for (var i = 0; i < this.customHandles.length; i++) {
      var temp = this.customHandles[i].shape.node.style.display;
      this.customHandles[i].redraw();
      this.customHandles[i].shape.node.style.display = temp;

      // Hides custom handles during text editing
      this.customHandles[i].shape.node.style.visibility = this.isCustomHandleVisible(this.customHandles[i])
        ? ''
        : 'hidden';
    }
  }
};

/**
 * Function: isCustomHandleVisible
 *
 * Returns true if the given custom handle is visible.
 */
mxEdgeHandler.prototype.isCustomHandleVisible = function (handle) {
  return !this.graph.isEditing() && this.state.view.graph.getSelectionCount() == 1;
};

/**
 * Function: hideHandles
 *
 * Shortcut to <hideSizers>.
 */
mxEdgeHandler.prototype.setHandlesVisible = function (visible) {
  if (this.bends != null) {
    for (var i = 0; i < this.bends.length; i++) {
      this.bends[i].node.style.display = visible ? '' : 'none';
    }
  }

  if (this.virtualBends != null) {
    for (var i = 0; i < this.virtualBends.length; i++) {
      this.virtualBends[i].node.style.display = visible ? '' : 'none';
    }
  }

  if (this.labelShape != null) {
    this.labelShape.node.style.display = visible ? '' : 'none';
  }

  if (this.customHandles != null) {
    for (var i = 0; i < this.customHandles.length; i++) {
      this.customHandles[i].setVisible(visible);
    }
  }
};

/**
 * Function: redrawInnerBends
 *
 * Updates and redraws the inner bends.
 *
 * Parameters:
 *
 * p0 - <mxPoint> that represents the location of the first point.
 * pe - <mxPoint> that represents the location of the last point.
 */
mxEdgeHandler.prototype.redrawInnerBends = function (p0, pe) {
  for (var i = 1; i < this.bends.length - 1; i++) {
    if (this.bends[i] != null) {
      if (this.abspoints[i] != null) {
        var x = this.abspoints[i].x;
        var y = this.abspoints[i].y;

        var b = this.bends[i].bounds;
        this.bends[i].node.style.visibility = 'visible';
        this.bends[i].bounds = new mxRectangle(
          Math.round(x - b.width / 2),
          Math.round(y - b.height / 2),
          b.width,
          b.height
        );

        if (this.manageLabelHandle) {
          this.checkLabelHandle(this.bends[i].bounds);
        } else if (
          this.handleImage == null &&
          this.labelShape.visible &&
          mxUtils.intersects(this.bends[i].bounds, this.labelShape.bounds)
        ) {
          w = mxConstants.HANDLE_SIZE + 3;
          h = mxConstants.HANDLE_SIZE + 3;
          this.bends[i].bounds = new mxRectangle(Math.round(x - w / 2), Math.round(y - h / 2), w, h);
        }

        this.bends[i].redraw();
      } else {
        this.bends[i].destroy();
        this.bends[i] = null;
      }
    }
  }
};

/**
 * Function: checkLabelHandle
 *
 * Checks if the label handle intersects the given bounds and moves it if it
 * intersects.
 */
mxEdgeHandler.prototype.checkLabelHandle = function (b) {
  if (this.labelShape != null) {
    var b2 = this.labelShape.bounds;

    if (mxUtils.intersects(b, b2)) {
      if (b.getCenterY() < b2.getCenterY()) {
        b2.y = b.y + b.height;
      } else {
        b2.y = b.y - b2.height;
      }
    }
  }
};

/**
 * Function: drawPreview
 *
 * Redraws the preview.
 */
mxEdgeHandler.prototype.drawPreview = function () {
  try {
    if (this.isLabel) {
      var b = this.labelShape.bounds;
      var bounds = new mxRectangle(
        Math.round(this.label.x - b.width / 2),
        Math.round(this.label.y - b.height / 2),
        b.width,
        b.height
      );

      if (!this.labelShape.bounds.equals(bounds)) {
        this.labelShape.bounds = bounds;
        this.labelShape.redraw();
      }
    }

    if (this.shape != null && !mxUtils.equalPoints(this.shape.points, this.abspoints)) {
      this.shape.apply(this.state);
      this.shape.points = this.abspoints.slice();
      this.shape.scale = this.state.view.scale;
      this.shape.isDashed = this.isSelectionDashed();
      this.shape.stroke = this.getSelectionColor();
      this.shape.strokewidth = this.getSelectionStrokeWidth() / this.shape.scale / this.shape.scale;
      this.shape.isShadow = false;
      this.shape.redraw();
    }

    this.updateParentHighlight();
  } catch (e) {
    // ignore
  }
};

/**
 * Function: refresh
 *
 * Refreshes the bends of this handler.
 */
mxEdgeHandler.prototype.refresh = function () {
  if (this.state != null) {
    this.abspoints = this.getSelectionPoints(this.state);
    this.points = [];

    if (this.bends != null) {
      this.destroyBends(this.bends);
      this.bends = this.createBends();
    }

    if (this.virtualBends != null) {
      this.destroyBends(this.virtualBends);
      this.virtualBends = this.createVirtualBends();
    }

    if (this.customHandles != null) {
      this.destroyBends(this.customHandles);
      this.customHandles = this.createCustomHandles();
    }

    // Puts label node on top of bends
    if (this.labelShape != null && this.labelShape.node != null && this.labelShape.node.parentNode != null) {
      this.labelShape.node.parentNode.appendChild(this.labelShape.node);
    }
  }
};

/**
 * Function: isDestroyed
 *
 * Returns true if <destroy> was called.
 */
mxEdgeHandler.prototype.isDestroyed = function () {
  return this.shape == null;
};

/**
 * Function: destroyBends
 *
 * Destroys all elements in <bends>.
 */
mxEdgeHandler.prototype.destroyBends = function (bends) {
  if (bends != null) {
    for (var i = 0; i < bends.length; i++) {
      if (bends[i] != null) {
        bends[i].destroy();
      }
    }
  }
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes. This does
 * normally not need to be called as handlers are destroyed automatically
 * when the corresponding cell is deselected.
 */
mxEdgeHandler.prototype.destroy = function () {
  if (this.escapeHandler != null) {
    this.state.view.graph.removeListener(this.escapeHandler);
    this.escapeHandler = null;
  }

  if (this.marker != null) {
    this.marker.destroy();
    this.marker = null;
  }

  if (this.shape != null) {
    this.shape.destroy();
    this.shape = null;
  }

  if (this.parentHighlight != null) {
    var parent = this.graph.model.getParent(this.state.cell);
    var pstate = this.graph.view.getState(parent);

    if (pstate != null && pstate.parentHighlight == this.parentHighlight) {
      pstate.parentHighlight = null;
    }

    this.parentHighlight.destroy();
    this.parentHighlight = null;
  }

  if (this.labelShape != null) {
    this.labelShape.destroy();
    this.labelShape = null;
  }

  if (this.constraintHandler != null) {
    this.constraintHandler.destroy();
    this.constraintHandler = null;
  }

  this.destroyBends(this.virtualBends);
  this.virtualBends = null;

  this.destroyBends(this.customHandles);
  this.customHandles = null;

  this.destroyBends(this.bends);
  this.bends = null;

  this.removeHint();
};

/**
 * handler/mxElbowEdgeHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxElbowEdgeHandler
 *
 * Graph event handler that reconnects edges and modifies control points and
 * the edge label location. Uses <mxTerminalMarker> for finding and
 * highlighting new source and target vertices. This handler is automatically
 * created in <mxGraph.createHandler>. It extends <mxEdgeHandler>.
 *
 * Constructor: mxEdgeHandler
 *
 * Constructs an edge handler for the specified <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> of the cell to be modified.
 */
function mxElbowEdgeHandler(state) {
  mxEdgeHandler.call(this, state);
}

/**
 * Extends mxEdgeHandler.
 */
mxUtils.extend(mxElbowEdgeHandler, mxEdgeHandler);

/**
 * Specifies if a double click on the middle handle should call
 * <mxGraph.flipEdge>. Default is true.
 */
mxElbowEdgeHandler.prototype.flipEnabled = true;

/**
 * Variable: doubleClickOrientationResource
 *
 * Specifies the resource key for the tooltip to be displayed on the single
 * control point for routed edges. If the resource for this key does not
 * exist then the value is used as the error message. Default is
 * 'doubleClickOrientation'.
 */
mxElbowEdgeHandler.prototype.doubleClickOrientationResource =
  mxClient.language != 'none' ? 'doubleClickOrientation' : '';

/**
 * Function: createBends
 *
 * Overrides <mxEdgeHandler.createBends> to create custom bends.
 */
mxElbowEdgeHandler.prototype.createBends = function () {
  var bends = [];

  // Source
  var bend = this.createHandleShape(0);
  this.initBend(bend);
  bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
  bends.push(bend);

  // Virtual
  bends.push(
    this.createVirtualBend(
      mxUtils.bind(this, function (evt) {
        if (!mxEvent.isConsumed(evt) && this.flipEnabled) {
          this.graph.flipEdge(this.state.cell, evt);
          mxEvent.consume(evt);
        }
      })
    )
  );

  this.points.push(new mxPoint(0, 0));

  // Target
  bend = this.createHandleShape(2);
  this.initBend(bend);
  bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
  bends.push(bend);

  return bends;
};

/**
 * Function: createVirtualBend
 *
 * Creates a virtual bend that supports double clicking and calls
 * <mxGraph.flipEdge>.
 */
mxElbowEdgeHandler.prototype.createVirtualBend = function (dblClickHandler) {
  var bend = this.createHandleShape();
  this.initBend(bend, dblClickHandler);

  bend.setCursor(this.getCursorForBend());

  if (!this.graph.isCellBendable(this.state.cell)) {
    bend.node.style.display = 'none';
  }

  return bend;
};

/**
 * Function: getCursorForBend
 *
 * Returns the cursor to be used for the bend.
 */
mxElbowEdgeHandler.prototype.getCursorForBend = function () {
  return this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.TopToBottom ||
    this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_TOPTOBOTTOM ||
    ((this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.ElbowConnector ||
      this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_ELBOW) &&
      this.state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)
    ? 'row-resize'
    : 'col-resize';
};

/**
 * Function: getTooltipForNode
 *
 * Returns the tooltip for the given node.
 */
mxElbowEdgeHandler.prototype.getTooltipForNode = function (node) {
  var tip = null;

  if (
    this.bends != null &&
    this.bends[1] != null &&
    (node == this.bends[1].node || node.parentNode == this.bends[1].node)
  ) {
    tip = this.doubleClickOrientationResource;
    tip = mxResources.get(tip) || tip; // translate
  }

  return tip;
};

/**
 * Function: convertPoint
 *
 * Converts the given point in-place from screen to unscaled, untranslated
 * graph coordinates and applies the grid.
 *
 * Parameters:
 *
 * point - <mxPoint> to be converted.
 * gridEnabled - Boolean that specifies if the grid should be applied.
 */
mxElbowEdgeHandler.prototype.convertPoint = function (point, gridEnabled) {
  var scale = this.graph.getView().getScale();
  var tr = this.graph.getView().getTranslate();
  var origin = this.state.origin;

  if (gridEnabled) {
    point.x = this.graph.snap(point.x);
    point.y = this.graph.snap(point.y);
  }

  point.x = Math.round(point.x / scale - tr.x - origin.x);
  point.y = Math.round(point.y / scale - tr.y - origin.y);

  return point;
};

/**
 * Function: redrawInnerBends
 *
 * Updates and redraws the inner bends.
 *
 * Parameters:
 *
 * p0 - <mxPoint> that represents the location of the first point.
 * pe - <mxPoint> that represents the location of the last point.
 */
mxElbowEdgeHandler.prototype.redrawInnerBends = function (p0, pe) {
  var g = this.graph.getModel().getGeometry(this.state.cell);
  var pts = this.state.absolutePoints;
  var pt = null;

  // Keeps the virtual bend on the edge shape
  if (pts.length > 1) {
    p0 = pts[1];
    pe = pts[pts.length - 2];
  } else if (g.points != null && g.points.length > 0) {
    pt = pts[0];
  }

  if (pt == null) {
    pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
  } else {
    pt = new mxPoint(
      this.graph.getView().scale * (pt.x + this.graph.getView().translate.x + this.state.origin.x),
      this.graph.getView().scale * (pt.y + this.graph.getView().translate.y + this.state.origin.y)
    );
  }

  // Makes handle slightly bigger if the yellow  label handle
  // exists and intersects this green handle
  var b = this.bends[1].bounds;
  var w = b.width;
  var h = b.height;
  var bounds = new mxRectangle(Math.round(pt.x - w / 2), Math.round(pt.y - h / 2), w, h);

  if (this.manageLabelHandle) {
    this.checkLabelHandle(bounds);
  } else if (
    this.handleImage == null &&
    this.labelShape.visible &&
    mxUtils.intersects(bounds, this.labelShape.bounds)
  ) {
    w = mxConstants.HANDLE_SIZE + 3;
    h = mxConstants.HANDLE_SIZE + 3;
    bounds = new mxRectangle(Math.floor(pt.x - w / 2), Math.floor(pt.y - h / 2), w, h);
  }

  this.bends[1].bounds = bounds;
  this.bends[1].redraw();

  if (this.manageLabelHandle) {
    this.checkLabelHandle(this.bends[1].bounds);
  }
};

/**
 * handler/mxEdgeSegmentHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
function mxEdgeSegmentHandler(state) {
  mxEdgeHandler.call(this, state);
}

/**
 * Extends mxEdgeHandler.
 */
mxUtils.extend(mxEdgeSegmentHandler, mxElbowEdgeHandler);

/**
 * Function: getCurrentPoints
 *
 * Returns the current absolute points.
 */
mxEdgeSegmentHandler.prototype.getCurrentPoints = function () {
  var pts = this.state.absolutePoints;

  if (pts != null) {
    // Special case for straight edges where we add a virtual middle handle for moving the edge
    var tol = Math.max(1, this.graph.view.scale);

    if (
      pts.length == 2 ||
      (pts.length == 3 &&
        ((Math.abs(pts[0].x - pts[1].x) < tol && Math.abs(pts[1].x - pts[2].x) < tol) ||
          (Math.abs(pts[0].y - pts[1].y) < tol && Math.abs(pts[1].y - pts[2].y) < tol)))
    ) {
      var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
      var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;

      pts = [pts[0], new mxPoint(cx, cy), new mxPoint(cx, cy), pts[pts.length - 1]];
    }
  }

  return pts;
};

/**
 * Function: getPreviewPoints
 *
 * Updates the given preview state taking into account the state of the constraint handler.
 */
mxEdgeSegmentHandler.prototype.getPreviewPoints = function (point) {
  if (this.isSource || this.isTarget) {
    return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments);
  } else {
    var pts = this.getCurrentPoints();
    var last = this.convertPoint(pts[0].clone(), false);
    point = this.convertPoint(point.clone(), false);
    var result = [];

    for (var i = 1; i < pts.length; i++) {
      var pt = this.convertPoint(pts[i].clone(), false);

      if (i == this.index) {
        if (Math.round(last.x - pt.x) == 0) {
          last.x = point.x;
          pt.x = point.x;
        }

        if (Math.round(last.y - pt.y) == 0) {
          last.y = point.y;
          pt.y = point.y;
        }
      }

      if (i < pts.length - 1) {
        result.push(pt);
      }

      last = pt;
    }

    // Replaces single point that intersects with source or target
    if (result.length == 1) {
      var source = this.state.getVisibleTerminalState(true);
      var target = this.state.getVisibleTerminalState(false);
      var scale = this.state.view.getScale();
      var tr = this.state.view.getTranslate();

      var x = result[0].x * scale + tr.x;
      var y = result[0].y * scale + tr.y;

      if ((source != null && mxUtils.contains(source, x, y)) || (target != null && mxUtils.contains(target, x, y))) {
        result = [point, point];
      }
    }

    return result;
  }
};

/**
 * Function: updatePreviewState
 *
 * Overridden to perform optimization of the edge style result.
 */
mxEdgeSegmentHandler.prototype.updatePreviewState = function (edge, point, terminalState, me) {
  mxEdgeHandler.prototype.updatePreviewState.apply(this, arguments);

  // Checks and corrects preview by running edge style again
  if (!this.isSource && !this.isTarget) {
    point = this.convertPoint(point.clone(), false);
    var pts = edge.absolutePoints;
    var pt0 = pts[0];
    var pt1 = pts[1];

    var result = [];

    for (var i = 2; i < pts.length; i++) {
      var pt2 = pts[i];

      // Merges adjacent segments only if more than 2 to allow for straight edges
      if (
        (Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&
        (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0)
      ) {
        result.push(this.convertPoint(pt1.clone(), false));
      }

      pt0 = pt1;
      pt1 = pt2;
    }

    var source = this.state.getVisibleTerminalState(true);
    var target = this.state.getVisibleTerminalState(false);
    var rpts = this.state.absolutePoints;

    // A straight line is represented by 3 handles
    if (
      result.length == 0 &&
      (Math.round(pts[0].x - pts[pts.length - 1].x) == 0 || Math.round(pts[0].y - pts[pts.length - 1].y) == 0)
    ) {
      result = [point, point];
    }
    // Handles special case of transitions from straight vertical to routed
    else if (
      pts.length == 5 &&
      result.length == 2 &&
      source != null &&
      target != null &&
      rpts != null &&
      Math.round(rpts[0].x - rpts[rpts.length - 1].x) == 0
    ) {
      var view = this.graph.getView();
      var scale = view.getScale();
      var tr = view.getTranslate();

      var y0 = view.getRoutingCenterY(source) / scale - tr.y;

      // Use fixed connection point y-coordinate if one exists
      var sc = this.graph.getConnectionConstraint(edge, source, true);

      if (sc != null) {
        var pt = this.graph.getConnectionPoint(source, sc);

        if (pt != null) {
          this.convertPoint(pt, false);
          y0 = pt.y;
        }
      }

      var ye = view.getRoutingCenterY(target) / scale - tr.y;

      // Use fixed connection point y-coordinate if one exists
      var tc = this.graph.getConnectionConstraint(edge, target, false);

      if (tc) {
        var pt = this.graph.getConnectionPoint(target, tc);

        if (pt != null) {
          this.convertPoint(pt, false);
          ye = pt.y;
        }
      }

      result = [new mxPoint(point.x, y0), new mxPoint(point.x, ye)];
    }

    this.points = result;

    // LATER: Check if points and result are different
    edge.view.updateFixedTerminalPoints(edge, source, target);
    edge.view.updatePoints(edge, this.points, source, target);
    edge.view.updateFloatingTerminalPoints(edge, source, target);
  }
};

/**
 * Overriden to merge edge segments.
 */
mxEdgeSegmentHandler.prototype.connect = function (edge, terminal, isSource, isClone, me) {
  var model = this.graph.getModel();
  var geo = model.getGeometry(edge);
  var result = null;

  // Merges adjacent edge segments
  if (geo != null && geo.points != null && geo.points.length > 0) {
    var pts = this.abspoints;
    var pt0 = pts[0];
    var pt1 = pts[1];
    result = [];

    for (var i = 2; i < pts.length; i++) {
      var pt2 = pts[i];

      // Merges adjacent segments only if more than 2 to allow for straight edges
      if (
        (Math.round(pt0.x - pt1.x) != 0 || Math.round(pt1.x - pt2.x) != 0) &&
        (Math.round(pt0.y - pt1.y) != 0 || Math.round(pt1.y - pt2.y) != 0)
      ) {
        result.push(this.convertPoint(pt1.clone(), false));
      }

      pt0 = pt1;
      pt1 = pt2;
    }
  }

  model.beginUpdate();
  try {
    if (result != null) {
      var geo = model.getGeometry(edge);

      if (geo != null) {
        geo = geo.clone();
        geo.points = result;

        model.setGeometry(edge, geo);
      }
    }

    edge = mxEdgeHandler.prototype.connect.apply(this, arguments);
  } finally {
    model.endUpdate();
  }

  return edge;
};

/**
 * Function: getTooltipForNode
 *
 * Returns no tooltips.
 */
mxEdgeSegmentHandler.prototype.getTooltipForNode = function (node) {
  return null;
};

/**
 * Function: start
 *
 * Starts the handling of the mouse gesture.
 */
mxEdgeSegmentHandler.prototype.start = function (x, y, index) {
  mxEdgeHandler.prototype.start.apply(this, arguments);

  if (this.bends != null && this.bends[index] != null && !this.isSource && !this.isTarget) {
    mxUtils.setOpacity(this.bends[index].node, 100);
  }
};

/**
 * Function: createBends
 *
 * Adds custom bends for the center of each segment.
 */
mxEdgeSegmentHandler.prototype.createBends = function () {
  var bends = [];

  // Source
  var bend = this.createHandleShape(0);
  this.initBend(bend);
  bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
  bends.push(bend);

  var pts = this.getCurrentPoints();

  // Waypoints (segment handles)
  if (this.graph.isCellBendable(this.state.cell)) {
    if (this.points == null) {
      this.points = [];
    }

    for (var i = 0; i < pts.length - 1; i++) {
      bend = this.createVirtualBend();
      bends.push(bend);
      var horizontal = Math.round(pts[i].x - pts[i + 1].x) == 0;

      // Special case where dy is 0 as well
      if (Math.round(pts[i].y - pts[i + 1].y) == 0 && i < pts.length - 2) {
        horizontal = Math.round(pts[i].x - pts[i + 2].x) == 0;
      }

      bend.setCursor(horizontal ? 'col-resize' : 'row-resize');
      this.points.push(new mxPoint(0, 0));
    }
  }

  // Target
  var bend = this.createHandleShape(pts.length);
  this.initBend(bend);
  bend.setCursor(mxConstants.CURSOR_TERMINAL_HANDLE);
  bends.push(bend);

  return bends;
};

/**
 * Function: redraw
 *
 * Overridden to invoke <refresh> before the redraw.
 */
mxEdgeSegmentHandler.prototype.redraw = function () {
  this.refresh();
  mxEdgeHandler.prototype.redraw.apply(this, arguments);
};

/**
 * Function: redrawInnerBends
 *
 * Updates the position of the custom bends.
 */
mxEdgeSegmentHandler.prototype.redrawInnerBends = function (p0, pe) {
  if (this.graph.isCellBendable(this.state.cell)) {
    var pts = this.getCurrentPoints();

    if (pts != null && pts.length > 1) {
      var straight = false;

      // Puts handle in the center of straight edges
      if (pts.length == 4 && Math.round(pts[1].x - pts[2].x) == 0 && Math.round(pts[1].y - pts[2].y) == 0) {
        straight = true;

        if (Math.round(pts[0].y - pts[pts.length - 1].y) == 0) {
          var cx = pts[0].x + (pts[pts.length - 1].x - pts[0].x) / 2;
          pts[1] = new mxPoint(cx, pts[1].y);
          pts[2] = new mxPoint(cx, pts[2].y);
        } else {
          var cy = pts[0].y + (pts[pts.length - 1].y - pts[0].y) / 2;
          pts[1] = new mxPoint(pts[1].x, cy);
          pts[2] = new mxPoint(pts[2].x, cy);
        }
      }

      for (var i = 0; i < pts.length - 1; i++) {
        if (this.bends[i + 1] != null) {
          var p0 = pts[i];
          var pe = pts[i + 1];
          var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
          var b = this.bends[i + 1].bounds;
          this.bends[i + 1].bounds = new mxRectangle(
            Math.floor(pt.x - b.width / 2),
            Math.floor(pt.y - b.height / 2),
            b.width,
            b.height
          );
          this.bends[i + 1].redraw();

          if (this.manageLabelHandle) {
            this.checkLabelHandle(this.bends[i + 1].bounds);
          }
        }
      }

      if (straight) {
        mxUtils.setOpacity(this.bends[1].node, this.virtualBendOpacity);
        mxUtils.setOpacity(this.bends[3].node, this.virtualBendOpacity);
      }
    }
  }
};

/**
 * handler/mxKeyHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxKeyHandler
 *
 * Event handler that listens to keystroke events. This is not a singleton,
 * however, it is normally only required once if the target is the document
 * element (default).
 *
 * This handler installs a key event listener in the topmost DOM node and
 * processes all events that originate from descandants of <mxGraph.container>
 * or from the topmost DOM node. The latter means that all unhandled keystrokes
 * are handled by this object regardless of the focused state of the <graph>.
 *
 * Example:
 *
 * The following example creates a key handler that listens to the delete key
 * (46) and deletes the selection cells if the graph is enabled.
 *
 * (code)
 * var keyHandler = new mxKeyHandler(graph);
 * keyHandler.bindKey(46, function(evt)
 * {
 *   if (graph.isEnabled())
 *   {
 *     graph.removeCells();
 *   }
 * });
 * (end)
 *
 * Keycodes:
 *
 * See http://tinyurl.com/yp8jgl or http://tinyurl.com/229yqw for a list of
 * keycodes or install a key event listener into the document element and print
 * the key codes of the respective events to the console.
 *
 * To support the Command key and the Control key on the Mac, the following
 * code can be used.
 *
 * (code)
 * keyHandler.getFunction = function(evt)
 * {
 *   if (evt != null)
 *   {
 *     return (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode];
 *   }
 *
 *   return null;
 * };
 * (end)
 *
 * Constructor: mxKeyHandler
 *
 * Constructs an event handler that executes functions bound to specific
 * keystrokes.
 *
 * Parameters:
 *
 * graph - Reference to the associated <mxGraph>.
 * target - Optional reference to the event target. If null, the document
 * element is used as the event target, that is, the object where the key
 * event listener is installed.
 */
function mxKeyHandler(graph, target) {
  if (graph != null) {
    this.graph = graph;
    this.target = target || document.documentElement;

    // Creates the arrays to map from keycodes to functions
    this.normalKeys = [];
    this.shiftKeys = [];
    this.controlKeys = [];
    this.controlShiftKeys = [];

    this.keydownHandler = mxUtils.bind(this, function (evt) {
      this.keyDown(evt);
    });

    // Installs the keystroke listener in the target
    mxEvent.addListener(this.target, 'keydown', this.keydownHandler);

    // Automatically deallocates memory in IE
    if (mxClient.IS_IE) {
      mxEvent.addListener(
        window,
        'unload',
        mxUtils.bind(this, function () {
          this.destroy();
        })
      );
    }
  }
}

/**
 * Variable: graph
 *
 * Reference to the <mxGraph> associated with this handler.
 */
mxKeyHandler.prototype.graph = null;

/**
 * Variable: target
 *
 * Reference to the target DOM, that is, the DOM node where the key event
 * listeners are installed.
 */
mxKeyHandler.prototype.target = null;

/**
 * Variable: normalKeys
 *
 * Maps from keycodes to functions for non-pressed control keys.
 */
mxKeyHandler.prototype.normalKeys = null;

/**
 * Variable: shiftKeys
 *
 * Maps from keycodes to functions for pressed shift keys.
 */
mxKeyHandler.prototype.shiftKeys = null;

/**
 * Variable: controlKeys
 *
 * Maps from keycodes to functions for pressed control keys.
 */
mxKeyHandler.prototype.controlKeys = null;

/**
 * Variable: controlShiftKeys
 *
 * Maps from keycodes to functions for pressed control and shift keys.
 */
mxKeyHandler.prototype.controlShiftKeys = null;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxKeyHandler.prototype.enabled = true;

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation returns
 * <enabled>.
 */
mxKeyHandler.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling by updating <enabled>.
 *
 * Parameters:
 *
 * enabled - Boolean that specifies the new enabled state.
 */
mxKeyHandler.prototype.setEnabled = function (enabled) {
  this.enabled = enabled;
};

/**
 * Function: bindKey
 *
 * Binds the specified keycode to the given function. This binding is used
 * if the control key is not pressed.
 *
 * Parameters:
 *
 * code - Integer that specifies the keycode.
 * funct - JavaScript function that takes the key event as an argument.
 */
mxKeyHandler.prototype.bindKey = function (code, funct) {
  this.normalKeys[code] = funct;
};

/**
 * Function: bindShiftKey
 *
 * Binds the specified keycode to the given function. This binding is used
 * if the shift key is pressed.
 *
 * Parameters:
 *
 * code - Integer that specifies the keycode.
 * funct - JavaScript function that takes the key event as an argument.
 */
mxKeyHandler.prototype.bindShiftKey = function (code, funct) {
  this.shiftKeys[code] = funct;
};

/**
 * Function: bindControlKey
 *
 * Binds the specified keycode to the given function. This binding is used
 * if the control key is pressed.
 *
 * Parameters:
 *
 * code - Integer that specifies the keycode.
 * funct - JavaScript function that takes the key event as an argument.
 */
mxKeyHandler.prototype.bindControlKey = function (code, funct) {
  this.controlKeys[code] = funct;
};

/**
 * Function: bindControlShiftKey
 *
 * Binds the specified keycode to the given function. This binding is used
 * if the control and shift key are pressed.
 *
 * Parameters:
 *
 * code - Integer that specifies the keycode.
 * funct - JavaScript function that takes the key event as an argument.
 */
mxKeyHandler.prototype.bindControlShiftKey = function (code, funct) {
  this.controlShiftKeys[code] = funct;
};

/**
 * Function: isControlDown
 *
 * Returns true if the control key is pressed. This uses <mxEvent.isControlDown>.
 *
 * Parameters:
 *
 * evt - Key event whose control key pressed state should be returned.
 */
mxKeyHandler.prototype.isControlDown = function (evt) {
  return mxEvent.isControlDown(evt);
};

/**
 * Function: getFunction
 *
 * Returns the function associated with the given key event or null if no
 * function is associated with the given event.
 *
 * Parameters:
 *
 * evt - Key event whose associated function should be returned.
 */
mxKeyHandler.prototype.getFunction = function (evt) {
  if (evt != null && !mxEvent.isAltDown(evt)) {
    if (this.isControlDown(evt)) {
      if (mxEvent.isShiftDown(evt)) {
        return this.controlShiftKeys[evt.keyCode];
      } else {
        return this.controlKeys[evt.keyCode];
      }
    } else {
      if (mxEvent.isShiftDown(evt)) {
        return this.shiftKeys[evt.keyCode];
      } else {
        return this.normalKeys[evt.keyCode];
      }
    }
  }

  return null;
};

/**
 * Function: isGraphEvent
 *
 * Returns true if the event should be processed by this handler, that is,
 * if the event source is either the target, one of its direct children, a
 * descendant of the <mxGraph.container>, or the <mxGraph.cellEditor> of the
 * <graph>.
 *
 * Parameters:
 *
 * evt - Key event that represents the keystroke.
 */
mxKeyHandler.prototype.isGraphEvent = function (evt) {
  var source = mxEvent.getSource(evt);

  // Accepts events from the target object or
  // in-place editing inside graph
  if (
    source == this.target ||
    source.parentNode == this.target ||
    (this.graph.cellEditor != null && this.graph.cellEditor.isEventSource(evt))
  ) {
    return true;
  }

  // Accepts events from inside the container
  return mxUtils.isAncestorNode(this.graph.container, source);
};

/**
 * Function: keyDown
 *
 * Handles the event by invoking the function bound to the respective keystroke
 * if <isEnabledForEvent> returns true for the given event and if
 * <isEventIgnored> returns false, except for escape for which
 * <isEventIgnored> is not invoked.
 *
 * Parameters:
 *
 * evt - Key event that represents the keystroke.
 */
mxKeyHandler.prototype.keyDown = function (evt) {
  if (this.isEnabledForEvent(evt)) {
    // Cancels the editing if escape is pressed
    if (evt.keyCode == 27 /* Escape */) {
      this.escape(evt);
    }

    // Invokes the function for the keystroke
    else if (!this.isEventIgnored(evt)) {
      var boundFunction = this.getFunction(evt);

      if (boundFunction != null) {
        boundFunction(evt);
        mxEvent.consume(evt);
      }
    }
  }
};

/**
 * Function: isEnabledForEvent
 *
 * Returns true if the given event should be handled. <isEventIgnored> is
 * called later if the event is not an escape key stroke, in which case
 * <escape> is called. This implementation returns true if <isEnabled>
 * returns true for both, this handler and <graph>, if the event is not
 * consumed and if <isGraphEvent> returns true.
 *
 * Parameters:
 *
 * evt - Key event that represents the keystroke.
 */
mxKeyHandler.prototype.isEnabledForEvent = function (evt) {
  return this.graph.isEnabled() && !mxEvent.isConsumed(evt) && this.isGraphEvent(evt) && this.isEnabled();
};

/**
 * Function: isEventIgnored
 *
 * Returns true if the given keystroke should be ignored. This returns
 * graph.isEditing().
 *
 * Parameters:
 *
 * evt - Key event that represents the keystroke.
 */
mxKeyHandler.prototype.isEventIgnored = function (evt) {
  return this.graph.isEditing();
};

/**
 * Function: escape
 *
 * Hook to process ESCAPE keystrokes. This implementation invokes
 * <mxGraph.stopEditing> to cancel the current editing, connecting
 * and/or other ongoing modifications.
 *
 * Parameters:
 *
 * evt - Key event that represents the keystroke. Possible keycode in this
 * case is 27 (ESCAPE).
 */
mxKeyHandler.prototype.escape = function (evt) {
  if (this.graph.isEscapeEnabled()) {
    this.graph.escape(evt);
  }
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its references into the DOM. This does
 * normally not need to be called, it is called automatically when the
 * window unloads (in IE).
 */
mxKeyHandler.prototype.destroy = function () {
  if (this.target != null && this.keydownHandler != null) {
    mxEvent.removeListener(this.target, 'keydown', this.keydownHandler);
    this.keydownHandler = null;
  }

  this.target = null;
};

/**
 * handler/mxTooltipHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxTooltipHandler
 *
 * Graph event handler that displays tooltips. <mxGraph.getTooltip> is used to
 * get the tooltip for a cell or handle. This handler is built-into
 * <mxGraph.tooltipHandler> and enabled using <mxGraph.setTooltips>.
 *
 * Example:
 *
 * (code>
 * new mxTooltipHandler(graph);
 * (end)
 *
 * Constructor: mxTooltipHandler
 *
 * Constructs an event handler that displays tooltips with the specified
 * delay (in milliseconds). If no delay is specified then a default delay
 * of 500 ms (0.5 sec) is used.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 * delay - Optional delay in milliseconds.
 */
function mxTooltipHandler(graph, delay) {
  if (graph != null) {
    this.graph = graph;
    this.delay = delay || 500;
    this.graph.addMouseListener(this);
  }
}

/**
 * Variable: zIndex
 *
 * Specifies the zIndex for the tooltip and its shadow. Default is 10005.
 */
mxTooltipHandler.prototype.zIndex = 10005;

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxTooltipHandler.prototype.graph = null;

/**
 * Variable: delay
 *
 * Delay to show the tooltip in milliseconds. Default is 500.
 */
mxTooltipHandler.prototype.delay = null;

/**
 * Variable: ignoreTouchEvents
 *
 * Specifies if touch and pen events should be ignored. Default is true.
 */
mxTooltipHandler.prototype.ignoreTouchEvents = true;

/**
 * Variable: hideOnHover
 *
 * Specifies if the tooltip should be hidden if the mouse is moved over the
 * current cell. Default is false.
 */
mxTooltipHandler.prototype.hideOnHover = false;

/**
 * Variable: destroyed
 *
 * True if this handler was destroyed using <destroy>.
 */
mxTooltipHandler.prototype.destroyed = false;

/**
 * Variable: enabled
 *
 * Specifies if events are handled. Default is true.
 */
mxTooltipHandler.prototype.enabled = true;

/**
 * Function: isEnabled
 *
 * Returns true if events are handled. This implementation
 * returns <enabled>.
 */
mxTooltipHandler.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Function: setEnabled
 *
 * Enables or disables event handling. This implementation
 * updates <enabled>.
 */
mxTooltipHandler.prototype.setEnabled = function (enabled) {
  this.enabled = enabled;
};

/**
 * Function: isHideOnHover
 *
 * Returns <hideOnHover>.
 */
mxTooltipHandler.prototype.isHideOnHover = function () {
  return this.hideOnHover;
};

/**
 * Function: setHideOnHover
 *
 * Sets <hideOnHover>.
 */
mxTooltipHandler.prototype.setHideOnHover = function (value) {
  this.hideOnHover = value;
};

/**
 * Function: init
 *
 * Initializes the DOM nodes required for this tooltip handler.
 */
mxTooltipHandler.prototype.init = function () {
  if (document.body != null) {
    this.div = document.createElement('div');
    this.div.className = 'mxTooltip';
    this.div.style.visibility = 'hidden';

    document.body.appendChild(this.div);

    mxEvent.addGestureListeners(
      this.div,
      mxUtils.bind(this, function (evt) {
        var source = mxEvent.getSource(evt);

        if (source.nodeName != 'A') {
          this.hideTooltip();
        }
      })
    );
  }
};

/**
 * Function: getStateForEvent
 *
 * Returns the <mxCellState> to be used for showing a tooltip for this event.
 */
mxTooltipHandler.prototype.getStateForEvent = function (me) {
  return me.getState();
};

/**
 * Function: mouseDown
 *
 * Handles the event by initiating a rubberband selection. By consuming the
 * event all subsequent events of the gesture are redirected to this
 * handler.
 */
mxTooltipHandler.prototype.mouseDown = function (sender, me) {
  this.reset(me, false);
  this.hideTooltip();
};

/**
 * Function: mouseMove
 *
 * Handles the event by updating the rubberband selection.
 */
mxTooltipHandler.prototype.mouseMove = function (sender, me) {
  if (me.getX() != this.lastX || me.getY() != this.lastY) {
    this.reset(me, true);
    var state = this.getStateForEvent(me);

    if (
      this.isHideOnHover() ||
      state != this.state ||
      (me.getSource() != this.node &&
        (!this.stateSource ||
          (state != null && this.stateSource == (me.isSource(state.shape) || !me.isSource(state.text)))))
    ) {
      this.hideTooltip();
    }
  }

  this.lastX = me.getX();
  this.lastY = me.getY();
};

/**
 * Function: mouseUp
 *
 * Handles the event by resetting the tooltip timer or hiding the existing
 * tooltip.
 */
mxTooltipHandler.prototype.mouseUp = function (sender, me) {
  this.reset(me, true);
  this.hideTooltip();
};

/**
 * Function: resetTimer
 *
 * Resets the timer.
 */
mxTooltipHandler.prototype.resetTimer = function () {
  if (this.thread != null) {
    window.clearTimeout(this.thread);
    this.thread = null;
  }
};

/**
 * Function: reset
 *
 * Resets and/or restarts the timer to trigger the display of the tooltip.
 */
mxTooltipHandler.prototype.reset = function (me, restart, state) {
  if (!this.ignoreTouchEvents || mxEvent.isMouseEvent(me.getEvent())) {
    this.resetTimer();
    state = state != null ? state : this.getStateForEvent(me);

    if (restart && this.isEnabled() && state != null && (this.div == null || this.div.style.visibility == 'hidden')) {
      var node = me.getSource();
      var x = me.getX();
      var y = me.getY();
      var stateSource = me.isSource(state.shape) || me.isSource(state.text);

      this.thread = window.setTimeout(
        mxUtils.bind(this, function () {
          if (!this.graph.isEditing() && !this.graph.popupMenuHandler.isMenuShowing() && !this.graph.isMouseDown) {
            // Uses information from inside event cause using the event at
            // this (delayed) point in time is not possible in IE as it no
            // longer contains the required information (member not found)
            var tip = this.graph.getTooltip(state, node, x, y);
            this.show(tip, x, y);
            this.state = state;
            this.node = node;
            this.stateSource = stateSource;
          }
        }),
        this.delay
      );
    }
  }
};

/**
 * Function: hide
 *
 * Hides the tooltip and resets the timer.
 */
mxTooltipHandler.prototype.hide = function () {
  this.resetTimer();
  this.hideTooltip();
};

/**
 * Function: hideTooltip
 *
 * Hides the tooltip.
 */
mxTooltipHandler.prototype.hideTooltip = function () {
  if (this.div != null) {
    this.div.style.visibility = 'hidden';
    this.div.innerHTML = '';
  }
};

/**
 * Function: show
 *
 * Shows the tooltip for the specified cell and optional index at the
 * specified location (with a vertical offset of 10 pixels).
 */
mxTooltipHandler.prototype.show = function (tip, x, y) {
  if (!this.destroyed && tip != null && tip.length > 0) {
    // Initializes the DOM nodes if required
    if (this.div == null) {
      this.init();
    }

    var origin = mxUtils.getScrollOrigin();

    this.div.style.zIndex = this.zIndex;
    this.div.style.left = x + origin.x + 'px';
    this.div.style.top = y + mxConstants.TOOLTIP_VERTICAL_OFFSET + origin.y + 'px';

    if (!mxUtils.isNode(tip)) {
      this.div.innerHTML = tip.replace(/\n/g, '<br>');
    } else {
      this.div.innerHTML = '';
      this.div.appendChild(tip);
    }

    this.div.style.visibility = '';
    mxUtils.fit(this.div);
  }
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes.
 */
mxTooltipHandler.prototype.destroy = function () {
  if (!this.destroyed) {
    this.graph.removeMouseListener(this);
    mxEvent.release(this.div);

    if (this.div != null && this.div.parentNode != null) {
      this.div.parentNode.removeChild(this.div);
    }

    this.destroyed = true;
    this.div = null;
  }
};

/**
 * handler/mxCellTracker.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCellTracker
 *
 * Event handler that highlights cells. Inherits from <mxCellMarker>.
 *
 * Example:
 *
 * (code)
 * new mxCellTracker(graph, '#00FF00');
 * (end)
 *
 * For detecting dragEnter, dragOver and dragLeave on cells, the following
 * code can be used:
 *
 * (code)
 * graph.addMouseListener(
 * {
 *   cell: null,
 *   mouseDown: function(sender, me) { },
 *   mouseMove: function(sender, me)
 *   {
 *     var tmp = me.getCell();
 *
 *     if (tmp != this.cell)
 *     {
 *       if (this.cell != null)
 *       {
 *         this.dragLeave(me.getEvent(), this.cell);
 *       }
 *
 *       this.cell = tmp;
 *
 *       if (this.cell != null)
 *       {
 *         this.dragEnter(me.getEvent(), this.cell);
 *       }
 *     }
 *
 *     if (this.cell != null)
 *     {
 *       this.dragOver(me.getEvent(), this.cell);
 *     }
 *   },
 *   mouseUp: function(sender, me) { },
 *   dragEnter: function(evt, cell)
 *   {
 *     mxLog.debug('dragEnter', cell.value);
 *   },
 *   dragOver: function(evt, cell)
 *   {
 *     mxLog.debug('dragOver', cell.value);
 *   },
 *   dragLeave: function(evt, cell)
 *   {
 *     mxLog.debug('dragLeave', cell.value);
 *   }
 * });
 * (end)
 *
 * Constructor: mxCellTracker
 *
 * Constructs an event handler that highlights cells.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 * color - Color of the highlight. Default is blue.
 * funct - Optional JavaScript function that is used to override
 * <mxCellMarker.getCell>.
 */
function mxCellTracker(graph, color, funct) {
  mxCellMarker.call(this, graph, color);

  this.graph.addMouseListener(this);

  if (funct != null) {
    this.getCell = funct;
  }

  // Automatic deallocation of memory
  if (mxClient.IS_IE) {
    mxEvent.addListener(
      window,
      'unload',
      mxUtils.bind(this, function () {
        this.destroy();
      })
    );
  }
}

/**
 * Extends mxCellMarker.
 */
mxUtils.extend(mxCellTracker, mxCellMarker);

/**
 * Function: mouseDown
 *
 * Ignores the event. The event is not consumed.
 */
mxCellTracker.prototype.mouseDown = function (sender, me) {};

/**
 * Function: mouseMove
 *
 * Handles the event by highlighting the cell under the mousepointer if it
 * is over the hotspot region of the cell.
 */
mxCellTracker.prototype.mouseMove = function (sender, me) {
  if (this.isEnabled()) {
    this.process(me);
  }
};

/**
 * Function: mouseUp
 *
 * Handles the event by reseting the highlight.
 */
mxCellTracker.prototype.mouseUp = function (sender, me) {};

/**
 * Function: destroy
 *
 * Destroys the object and all its resources and DOM nodes. This doesn't
 * normally need to be called. It is called automatically when the window
 * unloads.
 */
mxCellTracker.prototype.destroy = function () {
  if (!this.destroyed) {
    this.destroyed = true;

    this.graph.removeMouseListener(this);
    mxCellMarker.prototype.destroy.apply(this);
  }
};

/**
 * handler/mxCellHighlight.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCellHighlight
 *
 * A helper class to highlight cells. Here is an example for a given cell.
 *
 * (code)
 * var highlight = new mxCellHighlight(graph, '#ff0000', 2);
 * highlight.highlight(graph.view.getState(cell)));
 * (end)
 *
 * Constructor: mxCellHighlight
 *
 * Constructs a cell highlight.
 */
function mxCellHighlight(graph, highlightColor, strokeWidth, dashed) {
  if (graph != null) {
    this.graph = graph;
    this.highlightColor = highlightColor != null ? highlightColor : mxConstants.DEFAULT_VALID_COLOR;
    this.strokeWidth = strokeWidth != null ? strokeWidth : mxConstants.HIGHLIGHT_STROKEWIDTH;
    this.dashed = dashed != null ? dashed : false;
    this.opacity = mxConstants.HIGHLIGHT_OPACITY;

    // Updates the marker if the graph changes
    this.repaintHandler = mxUtils.bind(this, function () {
      // Updates reference to state
      if (this.state != null) {
        var tmp = this.graph.view.getState(this.state.cell);

        if (tmp == null) {
          this.hide();
        } else {
          this.state = tmp;
          this.repaint();
        }
      }
    });

    this.graph.getView().addListener(mxEvent.SCALE, this.repaintHandler);
    this.graph.getView().addListener(mxEvent.TRANSLATE, this.repaintHandler);
    this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler);
    this.graph.getModel().addListener(mxEvent.CHANGE, this.repaintHandler);

    // Hides the marker if the current root changes
    this.resetHandler = mxUtils.bind(this, function () {
      this.hide();
    });

    this.graph.getView().addListener(mxEvent.DOWN, this.resetHandler);
    this.graph.getView().addListener(mxEvent.UP, this.resetHandler);
  }
}

/**
 * Variable: keepOnTop
 *
 * Specifies if the highlights should appear on top of everything
 * else in the overlay pane. Default is false.
 */
mxCellHighlight.prototype.keepOnTop = false;

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxCellHighlight.prototype.graph = null;

/**
 * Variable: state
 *
 * Reference to the <mxCellState>.
 */
mxCellHighlight.prototype.state = null;

/**
 * Variable: spacing
 *
 * Specifies the spacing between the highlight for vertices and the vertex.
 * Default is 2.
 */
mxCellHighlight.prototype.spacing = 2;

/**
 * Variable: resetHandler
 *
 * Holds the handler that automatically invokes reset if the highlight
 * should be hidden.
 */
mxCellHighlight.prototype.resetHandler = null;

/**
 * Function: setHighlightColor
 *
 * Sets the color of the rectangle used to highlight drop targets.
 *
 * Parameters:
 *
 * color - String that represents the new highlight color.
 */
mxCellHighlight.prototype.setHighlightColor = function (color) {
  this.highlightColor = color;

  if (this.shape != null) {
    this.shape.stroke = color;
  }
};

/**
 * Function: drawHighlight
 *
 * Creates and returns the highlight shape for the given state.
 */
mxCellHighlight.prototype.drawHighlight = function () {
  this.shape = this.createShape();
  this.repaint();

  if (!this.keepOnTop && this.shape.node.parentNode.firstChild != this.shape.node) {
    this.shape.node.parentNode.insertBefore(this.shape.node, this.shape.node.parentNode.firstChild);
  }
};

/**
 * Function: createShape
 *
 * Creates and returns the highlight shape for the given state.
 */
mxCellHighlight.prototype.createShape = function () {
  var shape = this.graph.cellRenderer.createShape(this.state);

  shape.svgStrokeTolerance = this.graph.tolerance;
  shape.points = this.state.absolutePoints;
  shape.apply(this.state);
  shape.stroke = this.highlightColor;
  shape.opacity = this.opacity;
  shape.isDashed = this.dashed;
  shape.isShadow = false;

  shape.dialect = this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
  shape.init(this.graph.getView().getOverlayPane());
  mxEvent.redirectMouseEvents(shape.node, this.graph, this.state);

  if (this.graph.dialect != mxConstants.DIALECT_SVG) {
    shape.pointerEvents = false;
  } else {
    shape.svgPointerEvents = 'stroke';
  }

  return shape;
};

/**
 * Function: getStrokeWidth
 *
 * Returns the stroke width.
 */
mxCellHighlight.prototype.getStrokeWidth = function (state) {
  return this.strokeWidth;
};

/**
 * Function: repaint
 *
 * Updates the highlight after a change of the model or view.
 */
mxCellHighlight.prototype.repaint = function () {
  if (this.state != null && this.shape != null) {
    this.shape.scale = this.state.view.scale;

    if (this.graph.model.isEdge(this.state.cell)) {
      this.shape.strokewidth = this.getStrokeWidth();
      this.shape.points = this.state.absolutePoints;
      this.shape.outline = false;
    } else {
      this.shape.bounds = new mxRectangle(
        this.state.x - this.spacing,
        this.state.y - this.spacing,
        this.state.width + 2 * this.spacing,
        this.state.height + 2 * this.spacing
      );
      this.shape.rotation = Number(this.state.style[mxConstants.STYLE_ROTATION] || '0');
      this.shape.strokewidth = this.getStrokeWidth() / this.state.view.scale;
      this.shape.outline = true;
    }

    // Uses cursor from shape in highlight
    if (this.state.shape != null) {
      this.shape.setCursor(this.state.shape.getCursor());
    }

    // Workaround for event transparency in VML with transparent color
    // is to use a non-transparent color with near zero opacity
    if (mxClient.IS_QUIRKS || document.documentMode == 8) {
      if (this.shape.stroke == 'transparent') {
        // KNOWN: Quirks mode does not seem to catch events if
        // we do not force an update of the DOM via a change such
        // as mxLog.debug. Since IE6 is EOL we do not add a fix.
        this.shape.stroke = 'white';
        this.shape.opacity = 1;
      } else {
        this.shape.opacity = this.opacity;
      }
    }

    this.shape.redraw();
  }
};

/**
 * Function: hide
 *
 * Resets the state of the cell marker.
 */
mxCellHighlight.prototype.hide = function () {
  this.highlight(null);
};

/**
 * Function: mark
 *
 * Marks the <markedState> and fires a <mark> event.
 */
mxCellHighlight.prototype.highlight = function (state) {
  if (this.state != state) {
    if (this.shape != null) {
      this.shape.destroy();
      this.shape = null;
    }

    this.state = state;

    if (this.state != null) {
      this.drawHighlight();
    }
  }
};

/**
 * Function: isHighlightAt
 *
 * Returns true if this highlight is at the given position.
 */
mxCellHighlight.prototype.isHighlightAt = function (x, y) {
  var hit = false;

  // Quirks mode is currently not supported as it used a different coordinate system
  if (this.shape != null && document.elementFromPoint != null && !mxClient.IS_QUIRKS) {
    var elt = document.elementFromPoint(x, y);

    while (elt != null) {
      if (elt == this.shape.node) {
        hit = true;
        break;
      }

      elt = elt.parentNode;
    }
  }

  return hit;
};

/**
 * Function: destroy
 *
 * Destroys the handler and all its resources and DOM nodes.
 */
mxCellHighlight.prototype.destroy = function () {
  this.graph.getView().removeListener(this.resetHandler);
  this.graph.getView().removeListener(this.repaintHandler);
  this.graph.getModel().removeListener(this.repaintHandler);

  if (this.shape != null) {
    this.shape.destroy();
    this.shape = null;
  }
};

/**
 * editor/mxDefaultKeyHandler.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDefaultKeyHandler
 *
 * Binds keycodes to actionnames in an editor. This aggregates an internal
 * <handler> and extends the implementation of <mxKeyHandler.escape> to not
 * only cancel the editing, but also hide the properties dialog and fire an
 * <mxEditor.escape> event via <editor>. An instance of this class is created
 * by <mxEditor> and stored in <mxEditor.keyHandler>.
 *
 * Example:
 *
 * Bind the delete key to the delete action in an existing editor.
 *
 * (code)
 * var keyHandler = new mxDefaultKeyHandler(editor);
 * keyHandler.bindAction(46, 'delete');
 * (end)
 *
 * Codec:
 *
 * This class uses the <mxDefaultKeyHandlerCodec> to read configuration
 * data into an existing instance. See <mxDefaultKeyHandlerCodec> for a
 * description of the configuration format.
 *
 * Keycodes:
 *
 * See <mxKeyHandler>.
 *
 * An <mxEvent.ESCAPE> event is fired via the editor if the escape key is
 * pressed.
 *
 * Constructor: mxDefaultKeyHandler
 *
 * Constructs a new default key handler for the <mxEditor.graph> in the
 * given <mxEditor>. (The editor may be null if a prototypical instance for
 * a <mxDefaultKeyHandlerCodec> is created.)
 *
 * Parameters:
 *
 * editor - Reference to the enclosing <mxEditor>.
 */
function mxDefaultKeyHandler(editor) {
  if (editor != null) {
    this.editor = editor;
    this.handler = new mxKeyHandler(editor.graph);

    // Extends the escape function of the internal key
    // handle to hide the properties dialog and fire
    // the escape event via the editor instance
    var old = this.handler.escape;

    this.handler.escape = function (evt) {
      old.apply(this, arguments);
      editor.hideProperties();
      editor.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));
    };
  }
}

/**
 * Variable: editor
 *
 * Reference to the enclosing <mxEditor>.
 */
mxDefaultKeyHandler.prototype.editor = null;

/**
 * Variable: handler
 *
 * Holds the <mxKeyHandler> for key event handling.
 */
mxDefaultKeyHandler.prototype.handler = null;

/**
 * Function: bindAction
 *
 * Binds the specified keycode to the given action in <editor>. The
 * optional control flag specifies if the control key must be pressed
 * to trigger the action.
 *
 * Parameters:
 *
 * code - Integer that specifies the keycode.
 * action - Name of the action to execute in <editor>.
 * control - Optional boolean that specifies if control must be pressed.
 * Default is false.
 */
mxDefaultKeyHandler.prototype.bindAction = function (code, action, control) {
  var keyHandler = mxUtils.bind(this, function () {
    this.editor.execute(action);
  });

  // Binds the function to control-down keycode
  if (control) {
    this.handler.bindControlKey(code, keyHandler);
  }

  // Binds the function to the normal keycode
  else {
    this.handler.bindKey(code, keyHandler);
  }
};

/**
 * Function: destroy
 *
 * Destroys the <handler> associated with this object. This does normally
 * not need to be called, the <handler> is destroyed automatically when the
 * window unloads (in IE) by <mxEditor>.
 */
mxDefaultKeyHandler.prototype.destroy = function () {
  this.handler.destroy();
  this.handler = null;
};

/**
 * editor/mxDefaultPopupMenu.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDefaultPopupMenu
 *
 * Creates popupmenus for mouse events. This object holds an XML node
 * which is a description of the popup menu to be created. In
 * <createMenu>, the configuration is applied to the context and
 * the resulting menu items are added to the menu dynamically. See
 * <createMenu> for a description of the configuration format.
 *
 * This class does not create the DOM nodes required for the popup menu, it
 * only parses an XML description to invoke the respective methods on an
 * <mxPopupMenu> each time the menu is displayed.
 *
 * Codec:
 *
 * This class uses the <mxDefaultPopupMenuCodec> to read configuration
 * data into an existing instance, however, the actual parsing is done
 * by this class during program execution, so the format is described
 * below.
 *
 * Constructor: mxDefaultPopupMenu
 *
 * Constructs a new popupmenu-factory based on given configuration.
 *
 * Paramaters:
 *
 * config - XML node that contains the configuration data.
 */
function mxDefaultPopupMenu(config) {
  this.config = config;
}

/**
 * Variable: imageBasePath
 *
 * Base path for all icon attributes in the config. Default is null.
 */
mxDefaultPopupMenu.prototype.imageBasePath = null;

/**
 * Variable: config
 *
 * XML node used as the description of new menu items. This node is
 * used in <createMenu> to dynamically create the menu items if their
 * respective conditions evaluate to true for the given arguments.
 */
mxDefaultPopupMenu.prototype.config = null;

/**
 * Function: createMenu
 *
 * This function is called from <mxEditor> to add items to the
 * given menu based on <config>. The config is a sequence of
 * the following nodes and attributes.
 *
 * Child Nodes:
 *
 * add - Adds a new menu item. See below for attributes.
 * separator - Adds a separator. No attributes.
 * condition - Adds a custom condition. Name attribute.
 *
 * The add-node may have a child node that defines a function to be invoked
 * before the action is executed (or instead of an action to be executed).
 *
 * Attributes:
 *
 * as - Resource key for the label (needs entry in property file).
 * action - Name of the action to execute in enclosing editor.
 * icon - Optional icon (relative/absolute URL).
 * iconCls - Optional CSS class for the icon.
 * if - Optional name of condition that must be true (see below).
 * enabled-if - Optional name of condition that specifies if the menu item
 * should be enabled.
 * name - Name of custom condition. Only for condition nodes.
 *
 * Conditions:
 *
 * nocell - No cell under the mouse.
 * ncells - More than one cell selected.
 * notRoot - Drilling position is other than home.
 * cell - Cell under the mouse.
 * notEmpty - Exactly one cell with children under mouse.
 * expandable - Exactly one expandable cell under mouse.
 * collapsable - Exactly one collapsable cell under mouse.
 * validRoot - Exactly one cell which is a possible root under mouse.
 * swimlane - Exactly one cell which is a swimlane under mouse.
 *
 * Example:
 *
 * To add a new item for a given action to the popupmenu:
 *
 * (code)
 * <mxDefaultPopupMenu as="popupHandler">
 *   <add as="delete" action="delete" icon="images/delete.gif" if="cell"/>
 * </mxDefaultPopupMenu>
 * (end)
 *
 * To add a new item for a custom function:
 *
 * (code)
 * <mxDefaultPopupMenu as="popupHandler">
 *   <add as="action1"><![CDATA[
 *		function (editor, cell, evt)
 *		{
 *			editor.execute('action1', cell, 'myArg');
 *		}
 *   ]]></add>
 * </mxDefaultPopupMenu>
 * (end)
 *
 * The above example invokes action1 with an additional third argument via
 * the editor instance. The third argument is passed to the function that
 * defines action1. If the add-node has no action-attribute, then only the
 * function defined in the text content is executed, otherwise first the
 * function and then the action defined in the action-attribute is
 * executed. The function in the text content has 3 arguments, namely the
 * <mxEditor> instance, the <mxCell> instance under the mouse, and the
 * native mouse event.
 *
 * Custom Conditions:
 *
 * To add a new condition for popupmenu items:
 *
 * (code)
 * <condition name="condition1"><![CDATA[
 *   function (editor, cell, evt)
 *   {
 *     return cell != null;
 *   }
 * ]]></condition>
 * (end)
 *
 * The new condition can then be used in any item as follows:
 *
 * (code)
 * <add as="action1" action="action1" icon="action1.gif" if="condition1"/>
 * (end)
 *
 * The order in which the items and conditions appear is not significant as
 * all connditions are evaluated before any items are created.
 *
 * Parameters:
 *
 * editor - Enclosing <mxEditor> instance.
 * menu - <mxPopupMenu> that is used for adding items and separators.
 * cell - Optional <mxCell> which is under the mousepointer.
 * evt - Optional mouse event which triggered the menu.
 */
mxDefaultPopupMenu.prototype.createMenu = function (editor, menu, cell, evt) {
  if (this.config != null) {
    var conditions = this.createConditions(editor, cell, evt);
    var item = this.config.firstChild;

    this.addItems(editor, menu, cell, evt, conditions, item, null);
  }
};

/**
 * Function: addItems
 *
 * Recursively adds the given items and all of its children into the given menu.
 *
 * Parameters:
 *
 * editor - Enclosing <mxEditor> instance.
 * menu - <mxPopupMenu> that is used for adding items and separators.
 * cell - Optional <mxCell> which is under the mousepointer.
 * evt - Optional mouse event which triggered the menu.
 * conditions - Array of names boolean conditions.
 * item - XML node that represents the current menu item.
 * parent - DOM node that represents the parent menu item.
 */
mxDefaultPopupMenu.prototype.addItems = function (editor, menu, cell, evt, conditions, item, parent) {
  var addSeparator = false;

  while (item != null) {
    if (item.nodeName == 'add') {
      var condition = item.getAttribute('if');

      if (condition == null || conditions[condition]) {
        var as = item.getAttribute('as');
        as = mxResources.get(as) || as;
        var funct = mxUtils.eval(mxUtils.getTextContent(item));
        var action = item.getAttribute('action');
        var icon = item.getAttribute('icon');
        var iconCls = item.getAttribute('iconCls');
        var enabledCond = item.getAttribute('enabled-if');
        var enabled = enabledCond == null || conditions[enabledCond];

        if (addSeparator) {
          menu.addSeparator(parent);
          addSeparator = false;
        }

        if (icon != null && this.imageBasePath) {
          icon = this.imageBasePath + icon;
        }

        var row = this.addAction(menu, editor, as, icon, funct, action, cell, parent, iconCls, enabled);
        this.addItems(editor, menu, cell, evt, conditions, item.firstChild, row);
      }
    } else if (item.nodeName == 'separator') {
      addSeparator = true;
    }

    item = item.nextSibling;
  }
};

/**
 * Function: addAction
 *
 * Helper method to bind an action to a new menu item.
 *
 * Parameters:
 *
 * menu - <mxPopupMenu> that is used for adding items and separators.
 * editor - Enclosing <mxEditor> instance.
 * lab - String that represents the label of the menu item.
 * icon - Optional URL that represents the icon of the menu item.
 * action - Optional name of the action to execute in the given editor.
 * funct - Optional function to execute before the optional action. The
 * function takes an <mxEditor>, the <mxCell> under the mouse and the
 * mouse event that triggered the call.
 * cell - Optional <mxCell> to use as an argument for the action.
 * parent - DOM node that represents the parent menu item.
 * iconCls - Optional CSS class for the menu icon.
 * enabled - Optional boolean that specifies if the menu item is enabled.
 * Default is true.
 */
mxDefaultPopupMenu.prototype.addAction = function (
  menu,
  editor,
  lab,
  icon,
  funct,
  action,
  cell,
  parent,
  iconCls,
  enabled
) {
  var clickHandler = function (evt) {
    if (typeof funct == 'function') {
      funct.call(editor, editor, cell, evt);
    }

    if (action != null) {
      editor.execute(action, cell, evt);
    }
  };

  return menu.addItem(lab, icon, clickHandler, parent, iconCls, enabled);
};

/**
 * Function: createConditions
 *
 * Evaluates the default conditions for the given context.
 */
mxDefaultPopupMenu.prototype.createConditions = function (editor, cell, evt) {
  // Creates array with conditions
  var model = editor.graph.getModel();
  var childCount = model.getChildCount(cell);

  // Adds some frequently used conditions
  var conditions = [];
  conditions['nocell'] = cell == null;
  conditions['ncells'] = editor.graph.getSelectionCount() > 1;
  conditions['notRoot'] = model.getRoot() != model.getParent(editor.graph.getDefaultParent());
  conditions['cell'] = cell != null;

  var isCell = cell != null && editor.graph.getSelectionCount() == 1;
  conditions['nonEmpty'] = isCell && childCount > 0;
  conditions['expandable'] = isCell && editor.graph.isCellFoldable(cell, false);
  conditions['collapsable'] = isCell && editor.graph.isCellFoldable(cell, true);
  conditions['validRoot'] = isCell && editor.graph.isValidRoot(cell);
  conditions['emptyValidRoot'] = conditions['validRoot'] && childCount == 0;
  conditions['swimlane'] = isCell && editor.graph.isSwimlane(cell);

  // Evaluates dynamic conditions from config file
  var condNodes = this.config.getElementsByTagName('condition');

  for (var i = 0; i < condNodes.length; i++) {
    var funct = mxUtils.eval(mxUtils.getTextContent(condNodes[i]));
    var name = condNodes[i].getAttribute('name');

    if (name != null && typeof funct == 'function') {
      conditions[name] = funct(editor, cell, evt);
    }
  }

  return conditions;
};

/**
 * editor/mxDefaultToolbar.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDefaultToolbar
 *
 * Toolbar for the editor. This modifies the state of the graph
 * or inserts new cells upon mouse clicks.
 *
 * Example:
 *
 * Create a toolbar with a button to copy the selection into the clipboard,
 * and a combo box with one action to paste the selection from the clipboard
 * into the graph.
 *
 * (code)
 * var toolbar = new mxDefaultToolbar(container, editor);
 * toolbar.addItem('Copy', null, 'copy');
 *
 * var combo = toolbar.addActionCombo('More actions...');
 * toolbar.addActionOption(combo, 'Paste', 'paste');
 * (end)
 *
 * Codec:
 *
 * This class uses the <mxDefaultToolbarCodec> to read configuration
 * data into an existing instance. See <mxDefaultToolbarCodec> for a
 * description of the configuration format.
 *
 * Constructor: mxDefaultToolbar
 *
 * Constructs a new toolbar for the given container and editor. The
 * container and editor may be null if a prototypical instance for a
 * <mxDefaultKeyHandlerCodec> is created.
 *
 * Parameters:
 *
 * container - DOM node that contains the toolbar.
 * editor - Reference to the enclosing <mxEditor>.
 */
function mxDefaultToolbar(container, editor) {
  this.editor = editor;

  if (container != null && editor != null) {
    this.init(container);
  }
}

/**
 * Variable: editor
 *
 * Reference to the enclosing <mxEditor>.
 */
mxDefaultToolbar.prototype.editor = null;

/**
 * Variable: toolbar
 *
 * Holds the internal <mxToolbar>.
 */
mxDefaultToolbar.prototype.toolbar = null;

/**
 * Variable: resetHandler
 *
 * Reference to the function used to reset the <toolbar>.
 */
mxDefaultToolbar.prototype.resetHandler = null;

/**
 * Variable: spacing
 *
 * Defines the spacing between existing and new vertices in
 * gridSize units when a new vertex is dropped on an existing
 * cell. Default is 4 (40 pixels).
 */
mxDefaultToolbar.prototype.spacing = 4;

/**
 * Variable: connectOnDrop
 *
 * Specifies if elements should be connected if new cells are dropped onto
 * connectable elements. Default is false.
 */
mxDefaultToolbar.prototype.connectOnDrop = false;

/**
 * Function: init
 *
 * Constructs the <toolbar> for the given container and installs a listener
 * that updates the <mxEditor.insertFunction> on <editor> if an item is
 * selected in the toolbar. This assumes that <editor> is not null.
 *
 * Parameters:
 *
 * container - DOM node that contains the toolbar.
 */
mxDefaultToolbar.prototype.init = function (container) {
  if (container != null) {
    this.toolbar = new mxToolbar(container);

    // Installs the insert function in the editor if an item is
    // selected in the toolbar
    this.toolbar.addListener(
      mxEvent.SELECT,
      mxUtils.bind(this, function (sender, evt) {
        var funct = evt.getProperty('function');

        if (funct != null) {
          this.editor.insertFunction = mxUtils.bind(this, function () {
            funct.apply(this, arguments);
            this.toolbar.resetMode();
          });
        } else {
          this.editor.insertFunction = null;
        }
      })
    );

    // Resets the selected tool after a doubleclick or escape keystroke
    this.resetHandler = mxUtils.bind(this, function () {
      if (this.toolbar != null) {
        this.toolbar.resetMode(true);
      }
    });

    this.editor.graph.addListener(mxEvent.DOUBLE_CLICK, this.resetHandler);
    this.editor.addListener(mxEvent.ESCAPE, this.resetHandler);
  }
};

/**
 * Function: addItem
 *
 * Adds a new item that executes the given action in <editor>. The title,
 * icon and pressedIcon are used to display the toolbar item.
 *
 * Parameters:
 *
 * title - String that represents the title (tooltip) for the item.
 * icon - URL of the icon to be used for displaying the item.
 * action - Name of the action to execute when the item is clicked.
 * pressed - Optional URL of the icon for the pressed state.
 */
mxDefaultToolbar.prototype.addItem = function (title, icon, action, pressed) {
  var clickHandler = mxUtils.bind(this, function () {
    if (action != null && action.length > 0) {
      this.editor.execute(action);
    }
  });

  return this.toolbar.addItem(title, icon, clickHandler, pressed);
};

/**
 * Function: addSeparator
 *
 * Adds a vertical separator using the optional icon.
 *
 * Parameters:
 *
 * icon - Optional URL of the icon that represents the vertical separator.
 * Default is <mxClient.imageBasePath> + '/separator.gif'.
 */
mxDefaultToolbar.prototype.addSeparator = function (icon) {
  icon = icon || mxClient.imageBasePath + '/separator.gif';
  this.toolbar.addSeparator(icon);
};

/**
 * Function: addCombo
 *
 * Helper method to invoke <mxToolbar.addCombo> on <toolbar> and return the
 * resulting DOM node.
 */
mxDefaultToolbar.prototype.addCombo = function () {
  return this.toolbar.addCombo();
};

/**
 * Function: addActionCombo
 *
 * Helper method to invoke <mxToolbar.addActionCombo> on <toolbar> using
 * the given title and return the resulting DOM node.
 *
 * Parameters:
 *
 * title - String that represents the title of the combo.
 */
mxDefaultToolbar.prototype.addActionCombo = function (title) {
  return this.toolbar.addActionCombo(title);
};

/**
 * Function: addActionOption
 *
 * Binds the given action to a option with the specified label in the
 * given combo. Combo is an object returned from an earlier call to
 * <addCombo> or <addActionCombo>.
 *
 * Parameters:
 *
 * combo - DOM node that represents the combo box.
 * title - String that represents the title of the combo.
 * action - Name of the action to execute in <editor>.
 */
mxDefaultToolbar.prototype.addActionOption = function (combo, title, action) {
  var clickHandler = mxUtils.bind(this, function () {
    this.editor.execute(action);
  });

  this.addOption(combo, title, clickHandler);
};

/**
 * Function: addOption
 *
 * Helper method to invoke <mxToolbar.addOption> on <toolbar> and return
 * the resulting DOM node that represents the option.
 *
 * Parameters:
 *
 * combo - DOM node that represents the combo box.
 * title - String that represents the title of the combo.
 * value - Object that represents the value of the option.
 */
mxDefaultToolbar.prototype.addOption = function (combo, title, value) {
  return this.toolbar.addOption(combo, title, value);
};

/**
 * Function: addMode
 *
 * Creates an item for selecting the given mode in the <editor>'s graph.
 * Supported modenames are select, connect and pan.
 *
 * Parameters:
 *
 * title - String that represents the title of the item.
 * icon - URL of the icon that represents the item.
 * mode - String that represents the mode name to be used in
 * <mxEditor.setMode>.
 * pressed - Optional URL of the icon that represents the pressed state.
 * funct - Optional JavaScript function that takes the <mxEditor> as the
 * first and only argument that is executed after the mode has been
 * selected.
 */
mxDefaultToolbar.prototype.addMode = function (title, icon, mode, pressed, funct) {
  var clickHandler = mxUtils.bind(this, function () {
    this.editor.setMode(mode);

    if (funct != null) {
      funct(this.editor);
    }
  });

  return this.toolbar.addSwitchMode(title, icon, clickHandler, pressed);
};

/**
 * Function: addPrototype
 *
 * Creates an item for inserting a clone of the specified prototype cell into
 * the <editor>'s graph. The ptype may either be a cell or a function that
 * returns a cell.
 *
 * Parameters:
 *
 * title - String that represents the title of the item.
 * icon - URL of the icon that represents the item.
 * ptype - Function or object that represents the prototype cell. If ptype
 * is a function then it is invoked with no arguments to create new
 * instances.
 * pressed - Optional URL of the icon that represents the pressed state.
 * insert - Optional JavaScript function that handles an insert of the new
 * cell. This function takes the <mxEditor>, new cell to be inserted, mouse
 * event and optional <mxCell> under the mouse pointer as arguments.
 * toggle - Optional boolean that specifies if the item can be toggled.
 * Default is true.
 */
mxDefaultToolbar.prototype.addPrototype = function (title, icon, ptype, pressed, insert, toggle) {
  // Creates a wrapper function that is in charge of constructing
  // the new cell instance to be inserted into the graph
  var factory = mxUtils.bind(this, function () {
    if (typeof ptype == 'function') {
      return ptype();
    } else if (ptype != null) {
      return this.editor.graph.cloneCell(ptype);
    }

    return null;
  });

  // Defines the function for a click event on the graph
  // after this item has been selected in the toolbar
  var clickHandler = mxUtils.bind(this, function (evt, cell) {
    if (typeof insert == 'function') {
      insert(this.editor, factory(), evt, cell);
    } else {
      this.drop(factory(), evt, cell);
    }

    this.toolbar.resetMode();
    mxEvent.consume(evt);
  });

  var img = this.toolbar.addMode(title, icon, clickHandler, pressed, null, toggle);

  // Creates a wrapper function that calls the click handler without
  // the graph argument
  var dropHandler = function (graph, evt, cell) {
    clickHandler(evt, cell);
  };

  this.installDropHandler(img, dropHandler);

  return img;
};

/**
 * Function: drop
 *
 * Handles a drop from a toolbar item to the graph. The given vertex
 * represents the new cell to be inserted. This invokes <insert> or
 * <connect> depending on the given target cell.
 *
 * Parameters:
 *
 * vertex - <mxCell> to be inserted.
 * evt - Mouse event that represents the drop.
 * target - Optional <mxCell> that represents the drop target.
 */
mxDefaultToolbar.prototype.drop = function (vertex, evt, target) {
  var graph = this.editor.graph;
  var model = graph.getModel();

  if (target == null || model.isEdge(target) || !this.connectOnDrop || !graph.isCellConnectable(target)) {
    while (target != null && !graph.isValidDropTarget(target, [vertex], evt)) {
      target = model.getParent(target);
    }

    this.insert(vertex, evt, target);
  } else {
    this.connect(vertex, evt, target);
  }
};

/**
 * Function: insert
 *
 * Handles a drop by inserting the given vertex into the given parent cell
 * or the default parent if no parent is specified.
 *
 * Parameters:
 *
 * vertex - <mxCell> to be inserted.
 * evt - Mouse event that represents the drop.
 * parent - Optional <mxCell> that represents the parent.
 */
mxDefaultToolbar.prototype.insert = function (vertex, evt, target) {
  var graph = this.editor.graph;

  if (graph.canImportCell(vertex)) {
    var x = mxEvent.getClientX(evt);
    var y = mxEvent.getClientY(evt);
    var pt = mxUtils.convertPoint(graph.container, x, y);

    // Splits the target edge or inserts into target group
    if (graph.isSplitEnabled() && graph.isSplitTarget(target, [vertex], evt)) {
      return graph.splitEdge(target, [vertex], null, pt.x, pt.y);
    } else {
      return this.editor.addVertex(target, vertex, pt.x, pt.y);
    }
  }

  return null;
};

/**
 * Function: connect
 *
 * Handles a drop by connecting the given vertex to the given source cell.
 *
 * vertex - <mxCell> to be inserted.
 * evt - Mouse event that represents the drop.
 * source - Optional <mxCell> that represents the source terminal.
 */
mxDefaultToolbar.prototype.connect = function (vertex, evt, source) {
  var graph = this.editor.graph;
  var model = graph.getModel();

  if (source != null && graph.isCellConnectable(vertex) && graph.isEdgeValid(null, source, vertex)) {
    var edge = null;

    model.beginUpdate();
    try {
      var geo = model.getGeometry(source);
      var g = model.getGeometry(vertex).clone();

      // Moves the vertex away from the drop target that will
      // be used as the source for the new connection
      g.x = geo.x + (geo.width - g.width) / 2;
      g.y = geo.y + (geo.height - g.height) / 2;

      var step = this.spacing * graph.gridSize;
      var dist = model.getDirectedEdgeCount(source, true) * 20;

      if (this.editor.horizontalFlow) {
        g.x += (g.width + geo.width) / 2 + step + dist;
      } else {
        g.y += (g.height + geo.height) / 2 + step + dist;
      }

      vertex.setGeometry(g);

      // Fires two add-events with the code below - should be fixed
      // to only fire one add event for both inserts
      var parent = model.getParent(source);
      graph.addCell(vertex, parent);
      graph.constrainChild(vertex);

      // Creates the edge using the editor instance and calls
      // the second function that fires an add event
      edge = this.editor.createEdge(source, vertex);

      if (model.getGeometry(edge) == null) {
        var edgeGeometry = new mxGeometry();
        edgeGeometry.relative = true;

        model.setGeometry(edge, edgeGeometry);
      }

      graph.addEdge(edge, parent, source, vertex);
    } finally {
      model.endUpdate();
    }

    graph.setSelectionCells([vertex, edge]);
    graph.scrollCellToVisible(vertex);
  }
};

/**
 * Function: installDropHandler
 *
 * Makes the given img draggable using the given function for handling a
 * drop event.
 *
 * Parameters:
 *
 * img - DOM node that represents the image.
 * dropHandler - Function that handles a drop of the image.
 */
mxDefaultToolbar.prototype.installDropHandler = function (img, dropHandler) {
  var sprite = document.createElement('img');
  sprite.setAttribute('src', img.getAttribute('src'));

  // Handles delayed loading of the images
  var loader = mxUtils.bind(this, function (evt) {
    // Preview uses the image node with double size. Later this can be
    // changed to use a separate preview and guides, but for this the
    // dropHandler must use the additional x- and y-arguments and the
    // dragsource which makeDraggable returns much be configured to
    // use guides via mxDragSource.isGuidesEnabled.
    sprite.style.width = 2 * img.offsetWidth + 'px';
    sprite.style.height = 2 * img.offsetHeight + 'px';

    mxUtils.makeDraggable(img, this.editor.graph, dropHandler, sprite);
    mxEvent.removeListener(sprite, 'load', loader);
  });

  if (mxClient.IS_IE) {
    loader();
  } else {
    mxEvent.addListener(sprite, 'load', loader);
  }
};

/**
 * Function: destroy
 *
 * Destroys the <toolbar> associated with this object and removes all
 * installed listeners. This does normally not need to be called, the
 * <toolbar> is destroyed automatically when the window unloads (in IE) by
 * <mxEditor>.
 */
mxDefaultToolbar.prototype.destroy = function () {
  if (this.resetHandler != null) {
    this.editor.graph.removeListener('dblclick', this.resetHandler);
    this.editor.removeListener('escape', this.resetHandler);
    this.resetHandler = null;
  }

  if (this.toolbar != null) {
    this.toolbar.destroy();
    this.toolbar = null;
  }
};

/**
 * editor/mxEditor.js
 */
/**
 * Copyright (c) 2006-2019, JGraph Ltd
 * Copyright (c) 2006-2019, draw.io AG
 */
/**
 * Class: mxEditor
 *
 * Extends <mxEventSource> to implement an application wrapper for a graph that
 * adds <actions>, I/O using <mxCodec>, auto-layout using <mxLayoutManager>,
 * command history using <undoManager>, and standard dialogs and widgets, eg.
 * properties, help, outline, toolbar, and popupmenu. It also adds <templates>
 * to be used as cells in toolbars, auto-validation using the <validation>
 * flag, attribute cycling using <cycleAttributeValues>, higher-level events
 * such as <root>, and backend integration using <urlPost> and <urlImage>.
 *
 * Actions:
 *
 * Actions are functions stored in the <actions> array under their names. The
 * functions take the <mxEditor> as the first, and an optional <mxCell> as the
 * second argument and are invoked using <execute>. Any additional arguments
 * passed to execute are passed on to the action as-is.
 *
 * A list of built-in actions is available in the <addActions> description.
 *
 * Read/write Diagrams:
 *
 * To read a diagram from an XML string, for example from a textfield within the
 * page, the following code is used:
 *
 * (code)
 * var doc = mxUtils.parseXML(xmlString);
 * var node = doc.documentElement;
 * editor.readGraphModel(node);
 * (end)
 *
 * For reading a diagram from a remote location, use the <open> method.
 *
 * To save diagrams in XML on a server, you can set the <urlPost> variable.
 * This variable will be used in <getUrlPost> to construct a URL for the post
 * request that is issued in the <save> method. The post request contains the
 * XML representation of the diagram as returned by <writeGraphModel> in the
 * xml parameter.
 *
 * On the server side, the post request is processed using standard
 * technologies such as Java Servlets, CGI, .NET or ASP.
 *
 * Here are some examples of processing a post request in various languages.
 *
 * - Java: URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;")
 *
 * Note that the linefeeds should only be replaced if the XML is
 * processed in Java, for example when creating an image, but not
 * if the XML is passed back to the client-side.
 *
 * - .NET: HttpUtility.UrlDecode(context.Request.Params["xml"])
 * - PHP: urldecode($_POST["xml"])
 *
 * Creating images:
 *
 * A backend (Java, PHP or C#) is required for creating images. The
 * distribution contains an example for each backend (ImageHandler.java,
 * ImageHandler.cs and graph.php). More information about using a backend
 * to create images can be found in the readme.html files. Note that the
 * preview is implemented using VML/SVG in the browser and does not require
 * a backend. The backend is only required to creates images (bitmaps).
 *
 * Special characters:
 *
 * Note There are five characters that should always appear in XML content as
 * escapes, so that they do not interact with the syntax of the markup. These
 * are part of the language for all documents based on XML and for HTML.
 *
 * - &lt; (<)
 * - &gt; (>)
 * - &amp; (&)
 * - &quot; (")
 * - &apos; (')
 *
 * Although it is part of the XML language, &apos; is not defined in HTML.
 * For this reason the XHTML specification recommends instead the use of
 * &#39; if text may be passed to a HTML user agent.
 *
 * If you are having problems with special characters on the server-side then
 * you may want to try the <escapePostData> flag.
 *
 * For converting decimal escape sequences inside strings, a user has provided
 * us with the following function:
 *
 * (code)
 * function html2js(text)
 * {
 *   var entitySearch = /&#[0-9]+;/;
 *   var entity;
 *
 *   while (entity = entitySearch.exec(text))
 *   {
 *     var charCode = entity[0].substring(2, entity[0].length -1);
 *     text = text.substring(0, entity.index)
 *            + String.fromCharCode(charCode)
 *            + text.substring(entity.index + entity[0].length);
 *   }
 *
 *   return text;
 * }
 * (end)
 *
 * Otherwise try using hex escape sequences and the built-in unescape function
 * for converting such strings.
 *
 * Local Files:
 *
 * For saving and opening local files, no standardized method exists that
 * works across all browsers. The recommended way of dealing with local files
 * is to create a backend that streams the XML data back to the browser (echo)
 * as an attachment so that a Save-dialog is displayed on the client-side and
 * the file can be saved to the local disk.
 *
 * For example, in PHP the code that does this looks as follows.
 *
 * (code)
 * $xml = stripslashes($_POST["xml"]);
 * header("Content-Disposition: attachment; filename=\"diagram.xml\"");
 * echo($xml);
 * (end)
 *
 * To open a local file, the file should be uploaded via a form in the browser
 * and then opened from the server in the editor.
 *
 * Cell Properties:
 *
 * The properties displayed in the properties dialog are the attributes and
 * values of the cell's user object, which is an XML node. The XML node is
 * defined in the templates section of the config file.
 *
 * The templates are stored in <mxEditor.templates> and contain cells which
 * are cloned at insertion time to create new vertices by use of drag and
 * drop from the toolbar. Each entry in the toolbar for adding a new vertex
 * must refer to an existing template.
 *
 * In the following example, the task node is a business object and only the
 * mxCell node and its mxGeometry child contain graph information:
 *
 * (code)
 * <Task label="Task" description="">
 *   <mxCell vertex="true">
 *     <mxGeometry as="geometry" width="72" height="32"/>
 *   </mxCell>
 * </Task>
 * (end)
 *
 * The idea is that the XML representation is inverse from the in-memory
 * representation: The outer XML node is the user object and the inner node is
 * the cell. This means the user object of the cell is the Task node with no
 * children for the above example:
 *
 * (code)
 * <Task label="Task" description=""/>
 * (end)
 *
 * The Task node can have any tag name, attributes and child nodes. The
 * <mxCodec> will use the XML hierarchy as the user object, while removing the
 * "known annotations", such as the mxCell node. At save-time the cell data
 * will be "merged" back into the user object. The user object is only modified
 * via the properties dialog during the lifecycle of the cell.
 *
 * In the default implementation of <createProperties>, the user object's
 * attributes are put into a form for editing. Attributes are changed using
 * the <mxCellAttributeChange> action in the model. The dialog can be replaced
 * by overriding the <createProperties> hook or by replacing the showProperties
 * action in <actions>. Alternatively, the entry in the config file's popupmenu
 * section can be modified to invoke a different action.
 *
 * If you want to displey the properties dialog on a doubleclick, you can set
 * <mxEditor.dblClickAction> to showProperties as follows:
 *
 * (code)
 * editor.dblClickAction = 'showProperties';
 * (end)
 *
 * Popupmenu and Toolbar:
 *
 * The toolbar and popupmenu are typically configured using the respective
 * sections in the config file, that is, the popupmenu is defined as follows:
 *
 * (code)
 * <mxEditor>
 *   <mxDefaultPopupMenu as="popupHandler">
 * 		<add as="cut" action="cut" icon="images/cut.gif"/>
 *      ...
 * (end)
 *
 * New entries can be added to the toolbar by inserting an add-node into the
 * above configuration. Existing entries may be removed and changed by
 * modifying or removing the respective entries in the configuration.
 * The configuration is read by the <mxDefaultPopupMenuCodec>, the format of the
 * configuration is explained in <mxDefaultPopupMenu.decode>.
 *
 * The toolbar is defined in the mxDefaultToolbar section. Items can be added
 * and removed in this section.
 *
 * (code)
 * <mxEditor>
 *   <mxDefaultToolbar>
 *     <add as="save" action="save" icon="images/save.gif"/>
 *     <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"/>
 *     ...
 * (end)
 *
 * The format of the configuration is described in
 * <mxDefaultToolbarCodec.decode>.
 *
 * Ids:
 *
 * For the IDs, there is an implicit behaviour in <mxCodec>: It moves the Id
 * from the cell to the user object at encoding time and vice versa at decoding
 * time. For example, if the Task node from above has an id attribute, then
 * the <mxCell.id> of the corresponding cell will have this value. If there
 * is no Id collision in the model, then the cell may be retrieved using this
 * Id with the <mxGraphModel.getCell> function. If there is a collision, a new
 * Id will be created for the cell using <mxGraphModel.createId>. At encoding
 * time, this new Id will replace the value previously stored under the id
 * attribute in the Task node.
 *
 * See <mxEditorCodec>, <mxDefaultToolbarCodec> and <mxDefaultPopupMenuCodec>
 * for information about configuring the editor and user interface.
 *
 * Programmatically inserting cells:
 *
 * For inserting a new cell, say, by clicking a button in the document,
 * the following code can be used. This requires an reference to the editor.
 *
 * (code)
 * var userObject = new Object();
 * var parent = editor.graph.getDefaultParent();
 * var model = editor.graph.model;
 * model.beginUpdate();
 * try
 * {
 *   editor.graph.insertVertex(parent, null, userObject, 20, 20, 80, 30);
 * }
 * finally
 * {
 *   model.endUpdate();
 * }
 * (end)
 *
 * If a template cell from the config file should be inserted, then a clone
 * of the template can be created as follows. The clone is then inserted using
 * the add function instead of addVertex.
 *
 * (code)
 * var template = editor.templates['task'];
 * var clone = editor.graph.model.cloneCell(template);
 * (end)
 *
 * Resources:
 *
 * resources/editor - Language resources for mxEditor
 *
 * Callback: onInit
 *
 * Called from within the constructor. In the callback,
 * "this" refers to the editor instance.
 *
 * Cookie: mxgraph=seen
 *
 * Set when the editor is started. Never expires. Use
 * <resetFirstTime> to reset this cookie. This cookie
 * only exists if <onInit> is implemented.
 *
 * Event: mxEvent.OPEN
 *
 * Fires after a file was opened in <open>. The <code>filename</code> property
 * contains the filename that was used. The same value is also available in
 * <filename>.
 *
 * Event: mxEvent.SAVE
 *
 * Fires after the current file was saved in <save>. The <code>url</code>
 * property contains the URL that was used for saving.
 *
 * Event: mxEvent.POST
 *
 * Fires if a successful response was received in <postDiagram>. The
 * <code>request</code> property contains the <mxXmlRequest>, the
 * <code>url</code> and <code>data</code> properties contain the URL and the
 * data that were used in the post request.
 *
 * Event: mxEvent.ROOT
 *
 * Fires when the current root has changed, or when the title of the current
 * root has changed. This event has no properties.
 *
 * Event: mxEvent.BEFORE_ADD_VERTEX
 *
 * Fires before a vertex is added in <addVertex>. The <code>vertex</code>
 * property contains the new vertex and the <code>parent</code> property
 * contains its parent.
 *
 * Event: mxEvent.ADD_VERTEX
 *
 * Fires between begin- and endUpdate in <addVertex>. The <code>vertex</code>
 * property contains the vertex that is being inserted.
 *
 * Event: mxEvent.AFTER_ADD_VERTEX
 *
 * Fires after a vertex was inserted and selected in <addVertex>. The
 * <code>vertex</code> property contains the new vertex.
 *
 * Example:
 *
 * For starting an in-place edit after a new vertex has been added to the
 * graph, the following code can be used.
 *
 * (code)
 * editor.addListener(mxEvent.AFTER_ADD_VERTEX, function(sender, evt)
 * {
 *   var vertex = evt.getProperty('vertex');
 *
 *   if (editor.graph.isCellEditable(vertex))
 *   {
 *   	editor.graph.startEditingAtCell(vertex);
 *   }
 * });
 * (end)
 *
 * Event: mxEvent.ESCAPE
 *
 * Fires when the escape key is pressed. The <code>event</code> property
 * contains the key event.
 *
 * Constructor: mxEditor
 *
 * Constructs a new editor. This function invokes the <onInit> callback
 * upon completion.
 *
 * Example:
 *
 * (code)
 * var config = mxUtils.load('config/diagrameditor.xml').getDocumentElement();
 * var editor = new mxEditor(config);
 * (end)
 *
 * Parameters:
 *
 * config - Optional XML node that contains the configuration.
 */
function mxEditor(config) {
  this.actions = [];
  this.addActions();

  // Executes the following only if a document has been instanciated.
  // That is, don't execute when the editorcodec is setup.
  if (document.body != null) {
    // Defines instance fields
    this.cycleAttributeValues = [];
    this.popupHandler = new mxDefaultPopupMenu();
    this.undoManager = new mxUndoManager();

    // Creates the graph and toolbar without the containers
    this.graph = this.createGraph();
    this.toolbar = this.createToolbar();

    // Creates the global keyhandler (requires graph instance)
    this.keyHandler = new mxDefaultKeyHandler(this);

    // Configures the editor using the URI
    // which was passed to the ctor
    this.configure(config);

    // Assigns the swimlaneIndicatorColorAttribute on the graph
    this.graph.swimlaneIndicatorColorAttribute = this.cycleAttributeName;

    // Checks if the <onInit> hook has been set
    if (this.onInit != null) {
      // Invokes the <onInit> hook
      this.onInit();
    }

    // Automatic deallocation of memory
    if (mxClient.IS_IE) {
      mxEvent.addListener(
        window,
        'unload',
        mxUtils.bind(this, function () {
          this.destroy();
        })
      );
    }
  }
}

/**
 * Extends mxEventSource.
 */
mxEditor.prototype = new mxEventSource();
mxEditor.prototype.constructor = mxEditor;

/**
 * Group: Controls and Handlers
 */

/**
 * Variable: askZoomResource
 *
 * Specifies the resource key for the zoom dialog. If the resource for this
 * key does not exist then the value is used as the error message. Default
 * is 'askZoom'.
 */
mxEditor.prototype.askZoomResource = mxClient.language != 'none' ? 'askZoom' : '';

/**
 * Variable: lastSavedResource
 *
 * Specifies the resource key for the last saved info. If the resource for
 * this key does not exist then the value is used as the error message.
 * Default is 'lastSaved'.
 */
mxEditor.prototype.lastSavedResource = mxClient.language != 'none' ? 'lastSaved' : '';

/**
 * Variable: currentFileResource
 *
 * Specifies the resource key for the current file info. If the resource for
 * this key does not exist then the value is used as the error message.
 * Default is 'currentFile'.
 */
mxEditor.prototype.currentFileResource = mxClient.language != 'none' ? 'currentFile' : '';

/**
 * Variable: propertiesResource
 *
 * Specifies the resource key for the properties window title. If the
 * resource for this key does not exist then the value is used as the
 * error message. Default is 'properties'.
 */
mxEditor.prototype.propertiesResource = mxClient.language != 'none' ? 'properties' : '';

/**
 * Variable: tasksResource
 *
 * Specifies the resource key for the tasks window title. If the
 * resource for this key does not exist then the value is used as the
 * error message. Default is 'tasks'.
 */
mxEditor.prototype.tasksResource = mxClient.language != 'none' ? 'tasks' : '';

/**
 * Variable: helpResource
 *
 * Specifies the resource key for the help window title. If the
 * resource for this key does not exist then the value is used as the
 * error message. Default is 'help'.
 */
mxEditor.prototype.helpResource = mxClient.language != 'none' ? 'help' : '';

/**
 * Variable: outlineResource
 *
 * Specifies the resource key for the outline window title. If the
 * resource for this key does not exist then the value is used as the
 * error message. Default is 'outline'.
 */
mxEditor.prototype.outlineResource = mxClient.language != 'none' ? 'outline' : '';

/**
 * Variable: outline
 *
 * Reference to the <mxWindow> that contains the outline. The <mxOutline>
 * is stored in outline.outline.
 */
mxEditor.prototype.outline = null;

/**
 * Variable: graph
 *
 * Holds a <mxGraph> for displaying the diagram. The graph
 * is created in <setGraphContainer>.
 */
mxEditor.prototype.graph = null;

/**
 * Variable: graphRenderHint
 *
 * Holds the render hint used for creating the
 * graph in <setGraphContainer>. See <mxGraph>.
 * Default is null.
 */
mxEditor.prototype.graphRenderHint = null;

/**
 * Variable: toolbar
 *
 * Holds a <mxDefaultToolbar> for displaying the toolbar. The
 * toolbar is created in <setToolbarContainer>.
 */
mxEditor.prototype.toolbar = null;

/**
 * Variable: status
 *
 * DOM container that holds the statusbar. Default is null.
 * Use <setStatusContainer> to set this value.
 */
mxEditor.prototype.status = null;

/**
 * Variable: popupHandler
 *
 * Holds a <mxDefaultPopupMenu> for displaying
 * popupmenus.
 */
mxEditor.prototype.popupHandler = null;

/**
 * Variable: undoManager
 *
 * Holds an <mxUndoManager> for the command history.
 */
mxEditor.prototype.undoManager = null;

/**
 * Variable: keyHandler
 *
 * Holds a <mxDefaultKeyHandler> for handling keyboard events.
 * The handler is created in <setGraphContainer>.
 */
mxEditor.prototype.keyHandler = null;

/**
 * Group: Actions and Options
 */

/**
 * Variable: actions
 *
 * Maps from actionnames to actions, which are functions taking
 * the editor and the cell as arguments. Use <addAction>
 * to add or replace an action and <execute> to execute an action
 * by name, passing the cell to be operated upon as the second
 * argument.
 */
mxEditor.prototype.actions = null;

/**
 * Variable: dblClickAction
 *
 * Specifies the name of the action to be executed
 * when a cell is double clicked. Default is 'edit'.
 *
 * To handle a singleclick, use the following code.
 *
 * (code)
 * editor.graph.addListener(mxEvent.CLICK, function(sender, evt)
 * {
 *   var e = evt.getProperty('event');
 *   var cell = evt.getProperty('cell');
 *
 *   if (cell != null && !e.isConsumed())
 *   {
 *     // Do something useful with cell...
 *     e.consume();
 *   }
 * });
 * (end)
 */
mxEditor.prototype.dblClickAction = 'edit';

/**
 * Variable: swimlaneRequired
 *
 * Specifies if new cells must be inserted
 * into an existing swimlane. Otherwise, cells
 * that are not swimlanes can be inserted as
 * top-level cells. Default is false.
 */
mxEditor.prototype.swimlaneRequired = false;

/**
 * Variable: disableContextMenu
 *
 * Specifies if the context menu should be disabled in the graph container.
 * Default is true.
 */
mxEditor.prototype.disableContextMenu = true;

/**
 * Group: Templates
 */

/**
 * Variable: insertFunction
 *
 * Specifies the function to be used for inserting new
 * cells into the graph. This is assigned from the
 * <mxDefaultToolbar> if a vertex-tool is clicked.
 */
mxEditor.prototype.insertFunction = null;

/**
 * Variable: forcedInserting
 *
 * Specifies if a new cell should be inserted on a single
 * click even using <insertFunction> if there is a cell
 * under the mousepointer, otherwise the cell under the
 * mousepointer is selected. Default is false.
 */
mxEditor.prototype.forcedInserting = false;

/**
 * Variable: templates
 *
 * Maps from names to protoype cells to be used
 * in the toolbar for inserting new cells into
 * the diagram.
 */
mxEditor.prototype.templates = null;

/**
 * Variable: defaultEdge
 *
 * Prototype edge cell that is used for creating
 * new edges.
 */
mxEditor.prototype.defaultEdge = null;

/**
 * Variable: defaultEdgeStyle
 *
 * Specifies the edge style to be returned in <getEdgeStyle>.
 * Default is null.
 */
mxEditor.prototype.defaultEdgeStyle = null;

/**
 * Variable: defaultGroup
 *
 * Prototype group cell that is used for creating
 * new groups.
 */
mxEditor.prototype.defaultGroup = null;

/**
 * Variable: groupBorderSize
 *
 * Default size for the border of new groups. If null,
 * then then <mxGraph.gridSize> is used. Default is
 * null.
 */
mxEditor.prototype.groupBorderSize = null;

/**
 * Group: Backend Integration
 */

/**
 * Variable: filename
 *
 * Contains the URL of the last opened file as a string.
 * Default is null.
 */
mxEditor.prototype.filename = null;

/**
 * Variable: lineFeed
 *
 * Character to be used for encoding linefeeds in <save>. Default is '&#xa;'.
 */
mxEditor.prototype.linefeed = '&#xa;';

/**
 * Variable: postParameterName
 *
 * Specifies if the name of the post parameter that contains the diagram
 * data in a post request to the server. Default is 'xml'.
 */
mxEditor.prototype.postParameterName = 'xml';

/**
 * Variable: escapePostData
 *
 * Specifies if the data in the post request for saving a diagram
 * should be converted using encodeURIComponent. Default is true.
 */
mxEditor.prototype.escapePostData = true;

/**
 * Variable: urlPost
 *
 * Specifies the URL to be used for posting the diagram
 * to a backend in <save>.
 */
mxEditor.prototype.urlPost = null;

/**
 * Variable: urlImage
 *
 * Specifies the URL to be used for creating a bitmap of
 * the graph in the image action.
 */
mxEditor.prototype.urlImage = null;

/**
 * Group: Autolayout
 */

/**
 * Variable: horizontalFlow
 *
 * Specifies the direction of the flow
 * in the diagram. This is used in the
 * layout algorithms. Default is false,
 * ie. vertical flow.
 */
mxEditor.prototype.horizontalFlow = false;

/**
 * Variable: layoutDiagram
 *
 * Specifies if the top-level elements in the
 * diagram should be layed out using a vertical
 * or horizontal stack depending on the setting
 * of <horizontalFlow>. The spacing between the
 * swimlanes is specified by <swimlaneSpacing>.
 * Default is false.
 *
 * If the top-level elements are swimlanes, then
 * the intra-swimlane layout is activated by
 * the <layoutSwimlanes> switch.
 */
mxEditor.prototype.layoutDiagram = false;

/**
 * Variable: swimlaneSpacing
 *
 * Specifies the spacing between swimlanes if
 * automatic layout is turned on in
 * <layoutDiagram>. Default is 0.
 */
mxEditor.prototype.swimlaneSpacing = 0;

/**
 * Variable: maintainSwimlanes
 *
 * Specifies if the swimlanes should be kept at the same
 * width or height depending on the setting of
 * <horizontalFlow>.  Default is false.
 *
 * For horizontal flows, all swimlanes
 * have the same height and for vertical flows, all swimlanes
 * have the same width. Furthermore, the swimlanes are
 * automatically "stacked" if <layoutDiagram> is true.
 */
mxEditor.prototype.maintainSwimlanes = false;

/**
 * Variable: layoutSwimlanes
 *
 * Specifies if the children of swimlanes should
 * be layed out, either vertically or horizontally
 * depending on <horizontalFlow>.
 * Default is false.
 */
mxEditor.prototype.layoutSwimlanes = false;

/**
 * Group: Attribute Cycling
 */

/**
 * Variable: cycleAttributeValues
 *
 * Specifies the attribute values to be cycled when
 * inserting new swimlanes. Default is an empty
 * array.
 */
mxEditor.prototype.cycleAttributeValues = null;

/**
 * Variable: cycleAttributeIndex
 *
 * Index of the last consumed attribute index. If a new
 * swimlane is inserted, then the <cycleAttributeValues>
 * at this index will be used as the value for
 * <cycleAttributeName>. Default is 0.
 */
mxEditor.prototype.cycleAttributeIndex = 0;

/**
 * Variable: cycleAttributeName
 *
 * Name of the attribute to be assigned a <cycleAttributeValues>
 * when inserting new swimlanes. Default is 'fillColor'.
 */
mxEditor.prototype.cycleAttributeName = 'fillColor';

/**
 * Group: Windows
 */

/**
 * Variable: tasks
 *
 * Holds the <mxWindow> created in <showTasks>.
 */
mxEditor.prototype.tasks = null;

/**
 * Variable: tasksWindowImage
 *
 * Icon for the tasks window.
 */
mxEditor.prototype.tasksWindowImage = null;

/**
 * Variable: tasksTop
 *
 * Specifies the top coordinate of the tasks window in pixels.
 * Default is 20.
 */
mxEditor.prototype.tasksTop = 20;

/**
 * Variable: help
 *
 * Holds the <mxWindow> created in <showHelp>.
 */
mxEditor.prototype.help = null;

/**
 * Variable: helpWindowImage
 *
 * Icon for the help window.
 */
mxEditor.prototype.helpWindowImage = null;

/**
 * Variable: urlHelp
 *
 * Specifies the URL to be used for the contents of the
 * Online Help window. This is usually specified in the
 * resources file under urlHelp for language-specific
 * online help support.
 */
mxEditor.prototype.urlHelp = null;

/**
 * Variable: helpWidth
 *
 * Specifies the width of the help window in pixels.
 * Default is 300.
 */
mxEditor.prototype.helpWidth = 300;

/**
 * Variable: helpHeight
 *
 * Specifies the height of the help window in pixels.
 * Default is 260.
 */
mxEditor.prototype.helpHeight = 260;

/**
 * Variable: propertiesWidth
 *
 * Specifies the width of the properties window in pixels.
 * Default is 240.
 */
mxEditor.prototype.propertiesWidth = 240;

/**
 * Variable: propertiesHeight
 *
 * Specifies the height of the properties window in pixels.
 * If no height is specified then the window will be automatically
 * sized to fit its contents. Default is null.
 */
mxEditor.prototype.propertiesHeight = null;

/**
 * Variable: movePropertiesDialog
 *
 * Specifies if the properties dialog should be automatically
 * moved near the cell it is displayed for, otherwise the
 * dialog is not moved. This value is only taken into
 * account if the dialog is already visible. Default is false.
 */
mxEditor.prototype.movePropertiesDialog = false;

/**
 * Variable: validating
 *
 * Specifies if <mxGraph.validateGraph> should automatically be invoked after
 * each change. Default is false.
 */
mxEditor.prototype.validating = false;

/**
 * Variable: modified
 *
 * True if the graph has been modified since it was last saved.
 */
mxEditor.prototype.modified = false;

/**
 * Function: isModified
 *
 * Returns <modified>.
 */
mxEditor.prototype.isModified = function () {
  return this.modified;
};

/**
 * Function: setModified
 *
 * Sets <modified> to the specified boolean value.
 */
mxEditor.prototype.setModified = function (value) {
  this.modified = value;
};

/**
 * Function: addActions
 *
 * Adds the built-in actions to the editor instance.
 *
 * save - Saves the graph using <urlPost>.
 * print - Shows the graph in a new print preview window.
 * show - Shows the graph in a new window.
 * exportImage - Shows the graph as a bitmap image using <getUrlImage>.
 * refresh - Refreshes the graph's display.
 * cut - Copies the current selection into the clipboard
 * and removes it from the graph.
 * copy - Copies the current selection into the clipboard.
 * paste - Pastes the clipboard into the graph.
 * delete - Removes the current selection from the graph.
 * group - Puts the current selection into a new group.
 * ungroup - Removes the selected groups and selects the children.
 * undo - Undoes the last change on the graph model.
 * redo - Redoes the last change on the graph model.
 * zoom - Sets the zoom via a dialog.
 * zoomIn - Zooms into the graph.
 * zoomOut - Zooms out of the graph
 * actualSize - Resets the scale and translation on the graph.
 * fit - Changes the scale so that the graph fits into the window.
 * showProperties - Shows the properties dialog.
 * selectAll - Selects all cells.
 * selectNone - Clears the selection.
 * selectVertices - Selects all vertices.
 * selectEdges = Selects all edges.
 * edit - Starts editing the current selection cell.
 * enterGroup - Drills down into the current selection cell.
 * exitGroup - Moves up in the drilling hierachy
 * home - Moves to the topmost parent in the drilling hierarchy
 * selectPrevious - Selects the previous cell.
 * selectNext - Selects the next cell.
 * selectParent - Selects the parent of the selection cell.
 * selectChild - Selects the first child of the selection cell.
 * collapse - Collapses the currently selected cells.
 * expand - Expands the currently selected cells.
 * bold - Toggle bold text style.
 * italic - Toggle italic text style.
 * underline - Toggle underline text style.
 * alignCellsLeft - Aligns the selection cells at the left.
 * alignCellsCenter - Aligns the selection cells in the center.
 * alignCellsRight - Aligns the selection cells at the right.
 * alignCellsTop - Aligns the selection cells at the top.
 * alignCellsMiddle - Aligns the selection cells in the middle.
 * alignCellsBottom - Aligns the selection cells at the bottom.
 * alignFontLeft - Sets the horizontal text alignment to left.
 * alignFontCenter - Sets the horizontal text alignment to center.
 * alignFontRight - Sets the horizontal text alignment to right.
 * alignFontTop - Sets the vertical text alignment to top.
 * alignFontMiddle - Sets the vertical text alignment to middle.
 * alignFontBottom - Sets the vertical text alignment to bottom.
 * toggleTasks - Shows or hides the tasks window.
 * toggleHelp - Shows or hides the help window.
 * toggleOutline - Shows or hides the outline window.
 * toggleConsole - Shows or hides the console window.
 */
mxEditor.prototype.addActions = function () {
  this.addAction('save', function (editor) {
    editor.save();
  });

  this.addAction('print', function (editor) {
    var preview = new mxPrintPreview(editor.graph, 1);
    preview.open();
  });

  this.addAction('show', function (editor) {
    mxUtils.show(editor.graph, null, 10, 10);
  });

  this.addAction('exportImage', function (editor) {
    var url = editor.getUrlImage();

    if (url == null || mxClient.IS_LOCAL) {
      editor.execute('show');
    } else {
      var node = mxUtils.getViewXml(editor.graph, 1);
      var xml = mxUtils.getXml(node, '\n');

      mxUtils.submit(url, editor.postParameterName + '=' + encodeURIComponent(xml), document, '_blank');
    }
  });

  this.addAction('refresh', function (editor) {
    editor.graph.refresh();
  });

  this.addAction('cut', function (editor) {
    if (editor.graph.isEnabled()) {
      mxClipboard.cut(editor.graph);
    }
  });

  this.addAction('copy', function (editor) {
    if (editor.graph.isEnabled()) {
      mxClipboard.copy(editor.graph);
    }
  });

  this.addAction('paste', function (editor) {
    if (editor.graph.isEnabled()) {
      mxClipboard.paste(editor.graph);
    }
  });

  this.addAction('delete', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.removeCells();
    }
  });

  this.addAction('group', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.setSelectionCell(editor.groupCells());
    }
  });

  this.addAction('ungroup', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.setSelectionCells(editor.graph.ungroupCells());
    }
  });

  this.addAction('removeFromParent', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.removeCellsFromParent();
    }
  });

  this.addAction('undo', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.undo();
    }
  });

  this.addAction('redo', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.redo();
    }
  });

  this.addAction('zoomIn', function (editor) {
    editor.graph.zoomIn();
  });

  this.addAction('zoomOut', function (editor) {
    editor.graph.zoomOut();
  });

  this.addAction('actualSize', function (editor) {
    editor.graph.zoomActual();
  });

  this.addAction('fit', function (editor) {
    editor.graph.fit();
  });

  this.addAction('showProperties', function (editor, cell) {
    editor.showProperties(cell);
  });

  this.addAction('selectAll', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.selectAll();
    }
  });

  this.addAction('selectNone', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.clearSelection();
    }
  });

  this.addAction('selectVertices', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.selectVertices();
    }
  });

  this.addAction('selectEdges', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.selectEdges();
    }
  });

  this.addAction('edit', function (editor, cell) {
    if (editor.graph.isEnabled() && editor.graph.isCellEditable(cell)) {
      editor.graph.startEditingAtCell(cell);
    }
  });

  this.addAction('toBack', function (editor, cell) {
    if (editor.graph.isEnabled()) {
      editor.graph.orderCells(true);
    }
  });

  this.addAction('toFront', function (editor, cell) {
    if (editor.graph.isEnabled()) {
      editor.graph.orderCells(false);
    }
  });

  this.addAction('enterGroup', function (editor, cell) {
    editor.graph.enterGroup(cell);
  });

  this.addAction('exitGroup', function (editor) {
    editor.graph.exitGroup();
  });

  this.addAction('home', function (editor) {
    editor.graph.home();
  });

  this.addAction('selectPrevious', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.selectPreviousCell();
    }
  });

  this.addAction('selectNext', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.selectNextCell();
    }
  });

  this.addAction('selectParent', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.selectParentCell();
    }
  });

  this.addAction('selectChild', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.selectChildCell();
    }
  });

  this.addAction('collapse', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.foldCells(true);
    }
  });

  this.addAction('collapseAll', function (editor) {
    if (editor.graph.isEnabled()) {
      var cells = editor.graph.getChildVertices();
      editor.graph.foldCells(true, false, cells);
    }
  });

  this.addAction('expand', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.foldCells(false);
    }
  });

  this.addAction('expandAll', function (editor) {
    if (editor.graph.isEnabled()) {
      var cells = editor.graph.getChildVertices();
      editor.graph.foldCells(false, false, cells);
    }
  });

  this.addAction('bold', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_BOLD);
    }
  });

  this.addAction('italic', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_ITALIC);
    }
  });

  this.addAction('underline', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_UNDERLINE);
    }
  });

  this.addAction('alignCellsLeft', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.alignCells(mxConstants.ALIGN_LEFT);
    }
  });

  this.addAction('alignCellsCenter', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.alignCells(mxConstants.ALIGN_CENTER);
    }
  });

  this.addAction('alignCellsRight', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.alignCells(mxConstants.ALIGN_RIGHT);
    }
  });

  this.addAction('alignCellsTop', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.alignCells(mxConstants.ALIGN_TOP);
    }
  });

  this.addAction('alignCellsMiddle', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.alignCells(mxConstants.ALIGN_MIDDLE);
    }
  });

  this.addAction('alignCellsBottom', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.alignCells(mxConstants.ALIGN_BOTTOM);
    }
  });

  this.addAction('alignFontLeft', function (editor) {
    editor.graph.setCellStyles(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
  });

  this.addAction('alignFontCenter', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.setCellStyles(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
    }
  });

  this.addAction('alignFontRight', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.setCellStyles(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_RIGHT);
    }
  });

  this.addAction('alignFontTop', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_TOP);
    }
  });

  this.addAction('alignFontMiddle', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
    }
  });

  this.addAction('alignFontBottom', function (editor) {
    if (editor.graph.isEnabled()) {
      editor.graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_BOTTOM);
    }
  });

  this.addAction('zoom', function (editor) {
    var current = editor.graph.getView().scale * 100;
    var scale =
      parseFloat(mxUtils.prompt(mxResources.get(editor.askZoomResource) || editor.askZoomResource, current)) / 100;

    if (!isNaN(scale)) {
      editor.graph.getView().setScale(scale);
    }
  });

  this.addAction('toggleTasks', function (editor) {
    if (editor.tasks != null) {
      editor.tasks.setVisible(!editor.tasks.isVisible());
    } else {
      editor.showTasks();
    }
  });

  this.addAction('toggleHelp', function (editor) {
    if (editor.help != null) {
      editor.help.setVisible(!editor.help.isVisible());
    } else {
      editor.showHelp();
    }
  });

  this.addAction('toggleOutline', function (editor) {
    if (editor.outline == null) {
      editor.showOutline();
    } else {
      editor.outline.setVisible(!editor.outline.isVisible());
    }
  });

  this.addAction('toggleConsole', function (editor) {
    mxLog.setVisible(!mxLog.isVisible());
  });
};

/**
 * Function: configure
 *
 * Configures the editor using the specified node. To load the
 * configuration from a given URL the following code can be used to obtain
 * the XML node.
 *
 * (code)
 * var node = mxUtils.load(url).getDocumentElement();
 * (end)
 *
 * Parameters:
 *
 * node - XML node that contains the configuration.
 */
mxEditor.prototype.configure = function (node) {
  if (node != null) {
    // Creates a decoder for the XML data
    // and uses it to configure the editor
    var dec = new mxCodec(node.ownerDocument);
    dec.decode(node, this);

    // Resets the counters, modified state and
    // command history
    this.resetHistory();
  }
};

/**
 * Function: resetFirstTime
 *
 * Resets the cookie that is used to remember if the editor has already
 * been used.
 */
mxEditor.prototype.resetFirstTime = function () {
  document.cookie = 'mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/';
};

/**
 * Function: resetHistory
 *
 * Resets the command history, modified state and counters.
 */
mxEditor.prototype.resetHistory = function () {
  this.lastSnapshot = new Date().getTime();
  this.undoManager.clear();
  this.ignoredChanges = 0;
  this.setModified(false);
};

/**
 * Function: addAction
 *
 * Binds the specified actionname to the specified function.
 *
 * Parameters:
 *
 * actionname - String that specifies the name of the action
 * to be added.
 * funct - Function that implements the new action. The first
 * argument of the function is the editor it is used
 * with, the second argument is the cell it operates
 * upon.
 *
 * Example:
 * (code)
 * editor.addAction('test', function(editor, cell)
 * {
 * 		mxUtils.alert("test "+cell);
 * });
 * (end)
 */
mxEditor.prototype.addAction = function (actionname, funct) {
  this.actions[actionname] = funct;
};

/**
 * Function: execute
 *
 * Executes the function with the given name in <actions> passing the
 * editor instance and given cell as the first and second argument. All
 * additional arguments are passed to the action as well. This method
 * contains a try-catch block and displays an error message if an action
 * causes an exception. The exception is re-thrown after the error
 * message was displayed.
 *
 * Example:
 *
 * (code)
 * editor.execute("showProperties", cell);
 * (end)
 */
mxEditor.prototype.execute = function (actionname, cell, evt) {
  var action = this.actions[actionname];

  if (action != null) {
    try {
      // Creates the array of arguments by replacing the actionname
      // with the editor instance in the args of this function
      var args = arguments;
      args[0] = this;

      // Invokes the function on the editor using the args
      action.apply(this, args);
    } catch (e) {
      mxUtils.error('Cannot execute ' + actionname + ': ' + e.message, 280, true);

      throw e;
    }
  } else {
    mxUtils.error('Cannot find action ' + actionname, 280, true);
  }
};

/**
 * Function: addTemplate
 *
 * Adds the specified template under the given name in <templates>.
 */
mxEditor.prototype.addTemplate = function (name, template) {
  this.templates[name] = template;
};

/**
 * Function: getTemplate
 *
 * Returns the template for the given name.
 */
mxEditor.prototype.getTemplate = function (name) {
  return this.templates[name];
};

/**
 * Function: createGraph
 *
 * Creates the <graph> for the editor. The graph is created with no
 * container and is initialized from <setGraphContainer>.
 */
mxEditor.prototype.createGraph = function () {
  var graph = new mxGraph(null, null, this.graphRenderHint);

  // Enables rubberband, tooltips, panning
  graph.setTooltips(true);
  graph.setPanning(true);

  // Overrides the dblclick method on the graph to
  // invoke the dblClickAction for a cell and reset
  // the selection tool in the toolbar
  this.installDblClickHandler(graph);

  // Installs the command history
  this.installUndoHandler(graph);

  // Installs the handlers for the root event
  this.installDrillHandler(graph);

  // Installs the handler for validation
  this.installChangeHandler(graph);

  // Installs the handler for calling the
  // insert function and consume the
  // event if an insert function is defined
  this.installInsertHandler(graph);

  // Redirects the function for creating the
  // popupmenu items
  graph.popupMenuHandler.factoryMethod = mxUtils.bind(this, function (menu, cell, evt) {
    return this.createPopupMenu(menu, cell, evt);
  });

  // Redirects the function for creating
  // new connections in the diagram
  graph.connectionHandler.factoryMethod = mxUtils.bind(this, function (source, target) {
    return this.createEdge(source, target);
  });

  // Maintains swimlanes and installs autolayout
  this.createSwimlaneManager(graph);
  this.createLayoutManager(graph);

  return graph;
};

/**
 * Function: createSwimlaneManager
 *
 * Sets the graph's container using <mxGraph.init>.
 */
mxEditor.prototype.createSwimlaneManager = function (graph) {
  var swimlaneMgr = new mxSwimlaneManager(graph, false);

  swimlaneMgr.isHorizontal = mxUtils.bind(this, function () {
    return this.horizontalFlow;
  });

  swimlaneMgr.isEnabled = mxUtils.bind(this, function () {
    return this.maintainSwimlanes;
  });

  return swimlaneMgr;
};

/**
 * Function: createLayoutManager
 *
 * Creates a layout manager for the swimlane and diagram layouts, that
 * is, the locally defined inter- and intraswimlane layouts.
 */
mxEditor.prototype.createLayoutManager = function (graph) {
  var layoutMgr = new mxLayoutManager(graph);

  var self = this; // closure
  layoutMgr.getLayout = function (cell) {
    var layout = null;
    var model = self.graph.getModel();

    if (model.getParent(cell) != null) {
      // Executes the swimlane layout if a child of
      // a swimlane has been changed. The layout is
      // lazy created in createSwimlaneLayout.
      if (self.layoutSwimlanes && graph.isSwimlane(cell)) {
        if (self.swimlaneLayout == null) {
          self.swimlaneLayout = self.createSwimlaneLayout();
        }

        layout = self.swimlaneLayout;
      }

      // Executes the diagram layout if the modified
      // cell is a top-level cell. The layout is
      // lazy created in createDiagramLayout.
      else if (self.layoutDiagram && (graph.isValidRoot(cell) || model.getParent(model.getParent(cell)) == null)) {
        if (self.diagramLayout == null) {
          self.diagramLayout = self.createDiagramLayout();
        }

        layout = self.diagramLayout;
      }
    }

    return layout;
  };

  return layoutMgr;
};

/**
 * Function: setGraphContainer
 *
 * Sets the graph's container using <mxGraph.init>.
 */
mxEditor.prototype.setGraphContainer = function (container) {
  if (this.graph.container == null) {
    // Creates the graph instance inside the given container and render hint
    //this.graph = new mxGraph(container, null, this.graphRenderHint);
    this.graph.init(container);

    // Install rubberband selection as the last
    // action handler in the chain
    this.rubberband = new mxRubberband(this.graph);

    // Disables the context menu
    if (this.disableContextMenu) {
      mxEvent.disableContextMenu(container);
    }

    // Workaround for stylesheet directives in IE
    if (mxClient.IS_QUIRKS) {
      new mxDivResizer(container);
    }
  }
};

/**
 * Function: installDblClickHandler
 *
 * Overrides <mxGraph.dblClick> to invoke <dblClickAction>
 * on a cell and reset the selection tool in the toolbar.
 */
mxEditor.prototype.installDblClickHandler = function (graph) {
  // Installs a listener for double click events
  graph.addListener(
    mxEvent.DOUBLE_CLICK,
    mxUtils.bind(this, function (sender, evt) {
      var cell = evt.getProperty('cell');

      if (cell != null && graph.isEnabled() && this.dblClickAction != null) {
        this.execute(this.dblClickAction, cell);
        evt.consume();
      }
    })
  );
};

/**
 * Function: installUndoHandler
 *
 * Adds the <undoManager> to the graph model and the view.
 */
mxEditor.prototype.installUndoHandler = function (graph) {
  var listener = mxUtils.bind(this, function (sender, evt) {
    var edit = evt.getProperty('edit');
    this.undoManager.undoableEditHappened(edit);
  });

  graph.getModel().addListener(mxEvent.UNDO, listener);
  graph.getView().addListener(mxEvent.UNDO, listener);

  // Keeps the selection state in sync
  var undoHandler = function (sender, evt) {
    var changes = evt.getProperty('edit').changes;
    graph.setSelectionCells(graph.getSelectionCellsForChanges(changes));
  };

  this.undoManager.addListener(mxEvent.UNDO, undoHandler);
  this.undoManager.addListener(mxEvent.REDO, undoHandler);
};

/**
 * Function: installDrillHandler
 *
 * Installs listeners for dispatching the <root> event.
 */
mxEditor.prototype.installDrillHandler = function (graph) {
  var listener = mxUtils.bind(this, function (sender) {
    this.fireEvent(new mxEventObject(mxEvent.ROOT));
  });

  graph.getView().addListener(mxEvent.DOWN, listener);
  graph.getView().addListener(mxEvent.UP, listener);
};

/**
 * Function: installChangeHandler
 *
 * Installs the listeners required to automatically validate
 * the graph. On each change of the root, this implementation
 * fires a <root> event.
 */
mxEditor.prototype.installChangeHandler = function (graph) {
  var listener = mxUtils.bind(this, function (sender, evt) {
    // Updates the modified state
    this.setModified(true);

    // Automatically validates the graph
    // after each change
    if (this.validating == true) {
      graph.validateGraph();
    }

    // Checks if the root has been changed
    var changes = evt.getProperty('edit').changes;

    for (var i = 0; i < changes.length; i++) {
      var change = changes[i];

      if (
        change instanceof mxRootChange ||
        (change instanceof mxValueChange && change.cell == this.graph.model.root) ||
        (change instanceof mxCellAttributeChange && change.cell == this.graph.model.root)
      ) {
        this.fireEvent(new mxEventObject(mxEvent.ROOT));
        break;
      }
    }
  });

  graph.getModel().addListener(mxEvent.CHANGE, listener);
};

/**
 * Function: installInsertHandler
 *
 * Installs the handler for invoking <insertFunction> if
 * one is defined.
 */
mxEditor.prototype.installInsertHandler = function (graph) {
  var self = this; // closure
  var insertHandler = {
    mouseDown: function (sender, me) {
      if (self.insertFunction != null && !me.isPopupTrigger() && (self.forcedInserting || me.getState() == null)) {
        self.graph.clearSelection();
        self.insertFunction(me.getEvent(), me.getCell());

        // Consumes the rest of the events
        // for this gesture (down, move, up)
        this.isActive = true;
        me.consume();
      }
    },

    mouseMove: function (sender, me) {
      if (this.isActive) {
        me.consume();
      }
    },

    mouseUp: function (sender, me) {
      if (this.isActive) {
        this.isActive = false;
        me.consume();
      }
    },
  };

  graph.addMouseListener(insertHandler);
};

/**
 * Function: createDiagramLayout
 *
 * Creates the layout instance used to layout the
 * swimlanes in the diagram.
 */
mxEditor.prototype.createDiagramLayout = function () {
  var gs = this.graph.gridSize;
  var layout = new mxStackLayout(this.graph, !this.horizontalFlow, this.swimlaneSpacing, 2 * gs, 2 * gs);

  // Overrides isIgnored to only take into account swimlanes
  layout.isVertexIgnored = function (cell) {
    return !layout.graph.isSwimlane(cell);
  };

  return layout;
};

/**
 * Function: createSwimlaneLayout
 *
 * Creates the layout instance used to layout the
 * children of each swimlane.
 */
mxEditor.prototype.createSwimlaneLayout = function () {
  return new mxCompactTreeLayout(this.graph, this.horizontalFlow);
};

/**
 * Function: createToolbar
 *
 * Creates the <toolbar> with no container.
 */
mxEditor.prototype.createToolbar = function () {
  return new mxDefaultToolbar(null, this);
};

/**
 * Function: setToolbarContainer
 *
 * Initializes the toolbar for the given container.
 */
mxEditor.prototype.setToolbarContainer = function (container) {
  this.toolbar.init(container);

  // Workaround for stylesheet directives in IE
  if (mxClient.IS_QUIRKS) {
    new mxDivResizer(container);
  }
};

/**
 * Function: setStatusContainer
 *
 * Creates the <status> using the specified container.
 *
 * This implementation adds listeners in the editor to
 * display the last saved time and the current filename
 * in the status bar.
 *
 * Parameters:
 *
 * container - DOM node that will contain the statusbar.
 */
mxEditor.prototype.setStatusContainer = function (container) {
  if (this.status == null) {
    this.status = container;

    // Prints the last saved time in the status bar
    // when files are saved
    this.addListener(
      mxEvent.SAVE,
      mxUtils.bind(this, function () {
        var tstamp = new Date().toLocaleString();
        this.setStatus((mxResources.get(this.lastSavedResource) || this.lastSavedResource) + ': ' + tstamp);
      })
    );

    // Updates the statusbar to display the filename
    // when new files are opened
    this.addListener(
      mxEvent.OPEN,
      mxUtils.bind(this, function () {
        this.setStatus((mxResources.get(this.currentFileResource) || this.currentFileResource) + ': ' + this.filename);
      })
    );

    // Workaround for stylesheet directives in IE
    if (mxClient.IS_QUIRKS) {
      new mxDivResizer(container);
    }
  }
};

/**
 * Function: setStatus
 *
 * Display the specified message in the status bar.
 *
 * Parameters:
 *
 * message - String the specified the message to
 * be displayed.
 */
mxEditor.prototype.setStatus = function (message) {
  if (this.status != null && message != null) {
    this.status.innerHTML = message;
  }
};

/**
 * Function: setTitleContainer
 *
 * Creates a listener to update the inner HTML of the
 * specified DOM node with the value of <getTitle>.
 *
 * Parameters:
 *
 * container - DOM node that will contain the title.
 */
mxEditor.prototype.setTitleContainer = function (container) {
  this.addListener(
    mxEvent.ROOT,
    mxUtils.bind(this, function (sender) {
      container.innerHTML = this.getTitle();
    })
  );

  // Workaround for stylesheet directives in IE
  if (mxClient.IS_QUIRKS) {
    new mxDivResizer(container);
  }
};

/**
 * Function: treeLayout
 *
 * Executes a vertical or horizontal compact tree layout
 * using the specified cell as an argument. The cell may
 * either be a group or the root of a tree.
 *
 * Parameters:
 *
 * cell - <mxCell> to use in the compact tree layout.
 * horizontal - Optional boolean to specify the tree's
 * orientation. Default is true.
 */
mxEditor.prototype.treeLayout = function (cell, horizontal) {
  if (cell != null) {
    var layout = new mxCompactTreeLayout(this.graph, horizontal);
    layout.execute(cell);
  }
};

/**
 * Function: getTitle
 *
 * Returns the string value for the current root of the
 * diagram.
 */
mxEditor.prototype.getTitle = function () {
  var title = '';
  var graph = this.graph;
  var cell = graph.getCurrentRoot();

  while (cell != null && graph.getModel().getParent(graph.getModel().getParent(cell)) != null) {
    // Append each label of a valid root
    if (graph.isValidRoot(cell)) {
      title = ' > ' + graph.convertValueToString(cell) + title;
    }

    cell = graph.getModel().getParent(cell);
  }

  var prefix = this.getRootTitle();

  return prefix + title;
};

/**
 * Function: getRootTitle
 *
 * Returns the string value of the root cell in
 * <mxGraph.model>.
 */
mxEditor.prototype.getRootTitle = function () {
  var root = this.graph.getModel().getRoot();
  return this.graph.convertValueToString(root);
};

/**
 * Function: undo
 *
 * Undo the last change in <graph>.
 */
mxEditor.prototype.undo = function () {
  this.undoManager.undo();
};

/**
 * Function: redo
 *
 * Redo the last change in <graph>.
 */
mxEditor.prototype.redo = function () {
  this.undoManager.redo();
};

/**
 * Function: groupCells
 *
 * Invokes <createGroup> to create a new group cell and the invokes
 * <mxGraph.groupCells>, using the grid size of the graph as the spacing
 * in the group's content area.
 */
mxEditor.prototype.groupCells = function () {
  var border = this.groupBorderSize != null ? this.groupBorderSize : this.graph.gridSize;
  return this.graph.groupCells(this.createGroup(), border);
};

/**
 * Function: createGroup
 *
 * Creates and returns a clone of <defaultGroup> to be used
 * as a new group cell in <group>.
 */
mxEditor.prototype.createGroup = function () {
  var model = this.graph.getModel();

  return model.cloneCell(this.defaultGroup);
};

/**
 * Function: open
 *
 * Opens the specified file synchronously and parses it using
 * <readGraphModel>. It updates <filename> and fires an <open>-event after
 * the file has been opened. Exceptions should be handled as follows:
 *
 * (code)
 * try
 * {
 *   editor.open(filename);
 * }
 * catch (e)
 * {
 *   mxUtils.error('Cannot open ' + filename +
 *     ': ' + e.message, 280, true);
 * }
 * (end)
 *
 * Parameters:
 *
 * filename - URL of the file to be opened.
 */
mxEditor.prototype.open = function (filename) {
  if (filename != null) {
    var xml = mxUtils.load(filename).getXml();
    this.readGraphModel(xml.documentElement);
    this.filename = filename;

    this.fireEvent(new mxEventObject(mxEvent.OPEN, 'filename', filename));
  }
};

/**
 * Function: readGraphModel
 *
 * Reads the specified XML node into the existing graph model and resets
 * the command history and modified state.
 */
mxEditor.prototype.readGraphModel = function (node) {
  var dec = new mxCodec(node.ownerDocument);
  dec.decode(node, this.graph.getModel());
  this.resetHistory();
};

/**
 * Function: save
 *
 * Posts the string returned by <writeGraphModel> to the given URL or the
 * URL returned by <getUrlPost>. The actual posting is carried out by
 * <postDiagram>. If the URL is null then the resulting XML will be
 * displayed using <mxUtils.popup>. Exceptions should be handled as
 * follows:
 *
 * (code)
 * try
 * {
 *   editor.save();
 * }
 * catch (e)
 * {
 *   mxUtils.error('Cannot save : ' + e.message, 280, true);
 * }
 * (end)
 */
mxEditor.prototype.save = function (url, linefeed) {
  // Gets the URL to post the data to
  url = url || this.getUrlPost();

  // Posts the data if the URL is not empty
  if (url != null && url.length > 0) {
    var data = this.writeGraphModel(linefeed);
    this.postDiagram(url, data);

    // Resets the modified flag
    this.setModified(false);
  }

  // Dispatches a save event
  this.fireEvent(new mxEventObject(mxEvent.SAVE, 'url', url));
};

/**
 * Function: postDiagram
 *
 * Hook for subclassers to override the posting of a diagram
 * represented by the given node to the given URL. This fires
 * an asynchronous <post> event if the diagram has been posted.
 *
 * Example:
 *
 * To replace the diagram with the diagram in the response, use the
 * following code.
 *
 * (code)
 * editor.addListener(mxEvent.POST, function(sender, evt)
 * {
 *   // Process response (replace diagram)
 *   var req = evt.getProperty('request');
 *   var root = req.getDocumentElement();
 *   editor.graph.readGraphModel(root)
 * });
 * (end)
 */
mxEditor.prototype.postDiagram = function (url, data) {
  if (this.escapePostData) {
    data = encodeURIComponent(data);
  }

  mxUtils.post(
    url,
    this.postParameterName + '=' + data,
    mxUtils.bind(this, function (req) {
      this.fireEvent(new mxEventObject(mxEvent.POST, 'request', req, 'url', url, 'data', data));
    })
  );
};

/**
 * Function: writeGraphModel
 *
 * Hook to create the string representation of the diagram. The default
 * implementation uses an <mxCodec> to encode the graph model as
 * follows:
 *
 * (code)
 * var enc = new mxCodec();
 * var node = enc.encode(this.graph.getModel());
 * return mxUtils.getXml(node, this.linefeed);
 * (end)
 *
 * Parameters:
 *
 * linefeed - Optional character to be used as the linefeed. Default is
 * <linefeed>.
 */
mxEditor.prototype.writeGraphModel = function (linefeed) {
  linefeed = linefeed != null ? linefeed : this.linefeed;
  var enc = new mxCodec();
  var node = enc.encode(this.graph.getModel());

  return mxUtils.getXml(node, linefeed);
};

/**
 * Function: getUrlPost
 *
 * Returns the URL to post the diagram to. This is used
 * in <save>. The default implementation returns <urlPost>,
 * adding <code>?draft=true</code>.
 */
mxEditor.prototype.getUrlPost = function () {
  return this.urlPost;
};

/**
 * Function: getUrlImage
 *
 * Returns the URL to create the image with. This is typically
 * the URL of a backend which accepts an XML representation
 * of a graph view to create an image. The function is used
 * in the image action to create an image. This implementation
 * returns <urlImage>.
 */
mxEditor.prototype.getUrlImage = function () {
  return this.urlImage;
};

/**
 * Function: swapStyles
 *
 * Swaps the styles for the given names in the graph's
 * stylesheet and refreshes the graph.
 */
mxEditor.prototype.swapStyles = function (first, second) {
  var style = this.graph.getStylesheet().styles[second];
  this.graph.getView().getStylesheet().putCellStyle(second, this.graph.getStylesheet().styles[first]);
  this.graph.getStylesheet().putCellStyle(first, style);
  this.graph.refresh();
};

/**
 * Function: showProperties
 *
 * Creates and shows the properties dialog for the given
 * cell. The content area of the dialog is created using
 * <createProperties>.
 */
mxEditor.prototype.showProperties = function (cell) {
  cell = cell || this.graph.getSelectionCell();

  // Uses the root node for the properties dialog
  // if not cell was passed in and no cell is
  // selected
  if (cell == null) {
    cell = this.graph.getCurrentRoot();

    if (cell == null) {
      cell = this.graph.getModel().getRoot();
    }
  }

  if (cell != null) {
    // Makes sure there is no in-place editor in the
    // graph and computes the location of the dialog
    this.graph.stopEditing(true);

    var offset = mxUtils.getOffset(this.graph.container);
    var x = offset.x + 10;
    var y = offset.y;

    // Avoids moving the dialog if it is alredy open
    if (this.properties != null && !this.movePropertiesDialog) {
      x = this.properties.getX();
      y = this.properties.getY();
    }

    // Places the dialog near the cell for which it
    // displays the properties
    else {
      var bounds = this.graph.getCellBounds(cell);

      if (bounds != null) {
        x += bounds.x + Math.min(200, bounds.width);
        y += bounds.y;
      }
    }

    // Hides the existing properties dialog and creates a new one with the
    // contents created in the hook method
    this.hideProperties();
    var node = this.createProperties(cell);

    if (node != null) {
      // Displays the contents in a window and stores a reference to the
      // window for later hiding of the window
      this.properties = new mxWindow(
        mxResources.get(this.propertiesResource) || this.propertiesResource,
        node,
        x,
        y,
        this.propertiesWidth,
        this.propertiesHeight,
        false
      );
      this.properties.setVisible(true);
    }
  }
};

/**
 * Function: isPropertiesVisible
 *
 * Returns true if the properties dialog is currently visible.
 */
mxEditor.prototype.isPropertiesVisible = function () {
  return this.properties != null;
};

/**
 * Function: createProperties
 *
 * Creates and returns the DOM node that represents the contents
 * of the properties dialog for the given cell. This implementation
 * works for user objects that are XML nodes and display all the
 * node attributes in a form.
 */
mxEditor.prototype.createProperties = function (cell) {
  var model = this.graph.getModel();
  var value = model.getValue(cell);

  if (mxUtils.isNode(value)) {
    // Creates a form for the user object inside
    // the cell
    var form = new mxForm('properties');

    // Adds a readonly field for the cell id
    var id = form.addText('ID', cell.getId());
    id.setAttribute('readonly', 'true');

    var geo = null;
    var yField = null;
    var xField = null;
    var widthField = null;
    var heightField = null;

    // Adds fields for the location and size
    if (model.isVertex(cell)) {
      geo = model.getGeometry(cell);

      if (geo != null) {
        yField = form.addText('top', geo.y);
        xField = form.addText('left', geo.x);
        widthField = form.addText('width', geo.width);
        heightField = form.addText('height', geo.height);
      }
    }

    // Adds a field for the cell style
    var tmp = model.getStyle(cell);
    var style = form.addText('Style', tmp || '');

    // Creates textareas for each attribute of the
    // user object within the cell
    var attrs = value.attributes;
    var texts = [];

    for (var i = 0; i < attrs.length; i++) {
      // Creates a textarea with more lines for
      // the cell label
      var val = attrs[i].value;
      texts[i] = form.addTextarea(attrs[i].nodeName, val, attrs[i].nodeName == 'label' ? 4 : 2);
    }

    // Adds an OK and Cancel button to the dialog
    // contents and implements the respective
    // actions below

    // Defines the function to be executed when the
    // OK button is pressed in the dialog
    var okFunction = mxUtils.bind(this, function () {
      // Hides the dialog
      this.hideProperties();

      // Supports undo for the changes on the underlying
      // XML structure / XML node attribute changes.
      model.beginUpdate();
      try {
        if (geo != null) {
          geo = geo.clone();

          geo.x = parseFloat(xField.value);
          geo.y = parseFloat(yField.value);
          geo.width = parseFloat(widthField.value);
          geo.height = parseFloat(heightField.value);

          model.setGeometry(cell, geo);
        }

        // Applies the style
        if (style.value.length > 0) {
          model.setStyle(cell, style.value);
        } else {
          model.setStyle(cell, null);
        }

        // Creates an undoable change for each
        // attribute and executes it using the
        // model, which will also make the change
        // part of the current transaction
        for (var i = 0; i < attrs.length; i++) {
          var edit = new mxCellAttributeChange(cell, attrs[i].nodeName, texts[i].value);
          model.execute(edit);
        }

        // Checks if the graph wants cells to
        // be automatically sized and updates
        // the size as an undoable step if
        // the feature is enabled
        if (this.graph.isAutoSizeCell(cell)) {
          this.graph.updateCellSize(cell);
        }
      } finally {
        model.endUpdate();
      }
    });

    // Defines the function to be executed when the
    // Cancel button is pressed in the dialog
    var cancelFunction = mxUtils.bind(this, function () {
      // Hides the dialog
      this.hideProperties();
    });

    form.addButtons(okFunction, cancelFunction);

    return form.table;
  }

  return null;
};

/**
 * Function: hideProperties
 *
 * Hides the properties dialog.
 */
mxEditor.prototype.hideProperties = function () {
  if (this.properties != null) {
    this.properties.destroy();
    this.properties = null;
  }
};

/**
 * Function: showTasks
 *
 * Shows the tasks window. The tasks window is created using <createTasks>. The
 * default width of the window is 200 pixels, the y-coordinate of the location
 * can be specifies in <tasksTop> and the x-coordinate is right aligned with a
 * 20 pixel offset from the right border. To change the location of the tasks
 * window, the following code can be used:
 *
 * (code)
 * var oldShowTasks = mxEditor.prototype.showTasks;
 * mxEditor.prototype.showTasks = function()
 * {
 *   oldShowTasks.apply(this, arguments); // "supercall"
 *
 *   if (this.tasks != null)
 *   {
 *     this.tasks.setLocation(10, 10);
 *   }
 * };
 * (end)
 */
mxEditor.prototype.showTasks = function () {
  if (this.tasks == null) {
    var div = document.createElement('div');
    div.style.padding = '4px';
    div.style.paddingLeft = '20px';
    var w = document.body.clientWidth;
    var wnd = new mxWindow(mxResources.get(this.tasksResource) || this.tasksResource, div, w - 220, this.tasksTop, 200);
    wnd.setClosable(true);
    wnd.destroyOnClose = false;

    // Installs a function to update the contents
    // of the tasks window on every change of the
    // model, selection or root.
    var funct = mxUtils.bind(this, function (sender) {
      mxEvent.release(div);
      div.innerHTML = '';
      this.createTasks(div);
    });

    this.graph.getModel().addListener(mxEvent.CHANGE, funct);
    this.graph.getSelectionModel().addListener(mxEvent.CHANGE, funct);
    this.graph.addListener(mxEvent.ROOT, funct);

    // Assigns the icon to the tasks window
    if (this.tasksWindowImage != null) {
      wnd.setImage(this.tasksWindowImage);
    }

    this.tasks = wnd;
    this.createTasks(div);
  }

  this.tasks.setVisible(true);
};

/**
 * Function: refreshTasks
 *
 * Updates the contents of the tasks window using <createTasks>.
 */
mxEditor.prototype.refreshTasks = function (div) {
  if (this.tasks != null) {
    var div = this.tasks.content;
    mxEvent.release(div);
    div.innerHTML = '';
    this.createTasks(div);
  }
};

/**
 * Function: createTasks
 *
 * Updates the contents of the given DOM node to
 * display the tasks associated with the current
 * editor state. This is invoked whenever there
 * is a possible change of state in the editor.
 * Default implementation is empty.
 */
mxEditor.prototype.createTasks = function (div) {
  // override
};

/**
 * Function: showHelp
 *
 * Shows the help window. If the help window does not exist
 * then it is created using an iframe pointing to the resource
 * for the <code>urlHelp</code> key or <urlHelp> if the resource
 * is undefined.
 */
mxEditor.prototype.showHelp = function (tasks) {
  if (this.help == null) {
    var frame = document.createElement('iframe');
    frame.setAttribute('src', mxResources.get('urlHelp') || this.urlHelp);
    frame.setAttribute('height', '100%');
    frame.setAttribute('width', '100%');
    frame.setAttribute('frameBorder', '0');
    frame.style.backgroundColor = 'white';

    var w = document.body.clientWidth;
    var h = document.body.clientHeight || document.documentElement.clientHeight;

    var wnd = new mxWindow(
      mxResources.get(this.helpResource) || this.helpResource,
      frame,
      (w - this.helpWidth) / 2,
      (h - this.helpHeight) / 3,
      this.helpWidth,
      this.helpHeight
    );
    wnd.setMaximizable(true);
    wnd.setClosable(true);
    wnd.destroyOnClose = false;
    wnd.setResizable(true);

    // Assigns the icon to the help window
    if (this.helpWindowImage != null) {
      wnd.setImage(this.helpWindowImage);
    }

    // Workaround for ignored iframe height 100% in FF
    if (mxClient.IS_NS) {
      var handler = function (sender) {
        var h = wnd.div.offsetHeight;
        frame.setAttribute('height', h - 26 + 'px');
      };

      wnd.addListener(mxEvent.RESIZE_END, handler);
      wnd.addListener(mxEvent.MAXIMIZE, handler);
      wnd.addListener(mxEvent.NORMALIZE, handler);
      wnd.addListener(mxEvent.SHOW, handler);
    }

    this.help = wnd;
  }

  this.help.setVisible(true);
};

/**
 * Function: showOutline
 *
 * Shows the outline window. If the window does not exist, then it is
 * created using an <mxOutline>.
 */
mxEditor.prototype.showOutline = function () {
  var create = this.outline == null;

  if (create) {
    var div = document.createElement('div');

    div.style.overflow = 'hidden';
    div.style.position = 'relative';
    div.style.width = '100%';
    div.style.height = '100%';
    div.style.background = 'white';
    div.style.cursor = 'move';

    if (document.documentMode == 8) {
      div.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
    }

    var wnd = new mxWindow(
      mxResources.get(this.outlineResource) || this.outlineResource,
      div,
      600,
      480,
      200,
      200,
      false
    );

    // Creates the outline in the specified div
    // and links it to the existing graph
    var outline = new mxOutline(this.graph, div);
    wnd.setClosable(true);
    wnd.setResizable(true);
    wnd.destroyOnClose = false;

    wnd.addListener(mxEvent.RESIZE_END, function () {
      outline.update();
    });

    this.outline = wnd;
    this.outline.outline = outline;
  }

  // Finally shows the outline
  this.outline.setVisible(true);
  this.outline.outline.update(true);
};

/**
 * Function: setMode
 *
 * Puts the graph into the specified mode. The following modenames are
 * supported:
 *
 * select - Selects using the left mouse button, new connections
 * are disabled.
 * connect - Selects using the left mouse button or creates new
 * connections if mouse over cell hotspot. See <mxConnectionHandler>.
 * pan - Pans using the left mouse button, new connections are disabled.
 */
mxEditor.prototype.setMode = function (modename) {
  if (modename == 'select') {
    this.graph.panningHandler.useLeftButtonForPanning = false;
    this.graph.setConnectable(false);
  } else if (modename == 'connect') {
    this.graph.panningHandler.useLeftButtonForPanning = false;
    this.graph.setConnectable(true);
  } else if (modename == 'pan') {
    this.graph.panningHandler.useLeftButtonForPanning = true;
    this.graph.setConnectable(false);
  }
};

/**
 * Function: createPopupMenu
 *
 * Uses <popupHandler> to create the menu in the graph's
 * panning handler. The redirection is setup in
 * <setToolbarContainer>.
 */
mxEditor.prototype.createPopupMenu = function (menu, cell, evt) {
  this.popupHandler.createMenu(this, menu, cell, evt);
};

/**
 * Function: createEdge
 *
 * Uses <defaultEdge> as the prototype for creating new edges
 * in the connection handler of the graph. The style of the
 * edge will be overridden with the value returned by
 * <getEdgeStyle>.
 */
mxEditor.prototype.createEdge = function (source, target) {
  // Clones the defaultedge prototype
  var e = null;

  if (this.defaultEdge != null) {
    var model = this.graph.getModel();
    e = model.cloneCell(this.defaultEdge);
  } else {
    e = new mxCell('');
    e.setEdge(true);

    var geo = new mxGeometry();
    geo.relative = true;
    e.setGeometry(geo);
  }

  // Overrides the edge style
  var style = this.getEdgeStyle();

  if (style != null) {
    e.setStyle(style);
  }

  return e;
};

/**
 * Function: getEdgeStyle
 *
 * Returns a string identifying the style of new edges.
 * The function is used in <createEdge> when new edges
 * are created in the graph.
 */
mxEditor.prototype.getEdgeStyle = function () {
  return this.defaultEdgeStyle;
};

/**
 * Function: consumeCycleAttribute
 *
 * Returns the next attribute in <cycleAttributeValues>
 * or null, if not attribute should be used in the
 * specified cell.
 */
mxEditor.prototype.consumeCycleAttribute = function (cell) {
  return this.cycleAttributeValues != null && this.cycleAttributeValues.length > 0 && this.graph.isSwimlane(cell)
    ? this.cycleAttributeValues[this.cycleAttributeIndex++ % this.cycleAttributeValues.length]
    : null;
};

/**
 * Function: cycleAttribute
 *
 * Uses the returned value from <consumeCycleAttribute>
 * as the value for the <cycleAttributeName> key in
 * the given cell's style.
 */
mxEditor.prototype.cycleAttribute = function (cell) {
  if (this.cycleAttributeName != null) {
    var value = this.consumeCycleAttribute(cell);

    if (value != null) {
      cell.setStyle(cell.getStyle() + ';' + this.cycleAttributeName + '=' + value);
    }
  }
};

/**
 * Function: addVertex
 *
 * Adds the given vertex as a child of parent at the specified
 * x and y coordinate and fires an <addVertex> event.
 */
mxEditor.prototype.addVertex = function (parent, vertex, x, y) {
  var model = this.graph.getModel();

  while (parent != null && !this.graph.isValidDropTarget(parent)) {
    parent = model.getParent(parent);
  }

  parent = parent != null ? parent : this.graph.getSwimlaneAt(x, y);
  var scale = this.graph.getView().scale;

  var geo = model.getGeometry(vertex);
  var pgeo = model.getGeometry(parent);

  if (this.graph.isSwimlane(vertex) && !this.graph.swimlaneNesting) {
    parent = null;
  } else if (parent == null && this.swimlaneRequired) {
    return null;
  } else if (parent != null && pgeo != null) {
    // Keeps vertex inside parent
    var state = this.graph.getView().getState(parent);

    if (state != null) {
      x -= state.origin.x * scale;
      y -= state.origin.y * scale;

      if (this.graph.isConstrainedMoving) {
        var width = geo.width;
        var height = geo.height;
        var tmp = state.x + state.width;

        if (x + width > tmp) {
          x -= x + width - tmp;
        }

        tmp = state.y + state.height;

        if (y + height > tmp) {
          y -= y + height - tmp;
        }
      }
    } else if (pgeo != null) {
      x -= pgeo.x * scale;
      y -= pgeo.y * scale;
    }
  }

  geo = geo.clone();
  geo.x = this.graph.snap(x / scale - this.graph.getView().translate.x - this.graph.gridSize / 2);
  geo.y = this.graph.snap(y / scale - this.graph.getView().translate.y - this.graph.gridSize / 2);
  vertex.setGeometry(geo);

  if (parent == null) {
    parent = this.graph.getDefaultParent();
  }

  this.cycleAttribute(vertex);
  this.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX, 'vertex', vertex, 'parent', parent));

  model.beginUpdate();
  try {
    vertex = this.graph.addCell(vertex, parent);

    if (vertex != null) {
      this.graph.constrainChild(vertex);

      this.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX, 'vertex', vertex));
    }
  } finally {
    model.endUpdate();
  }

  if (vertex != null) {
    this.graph.setSelectionCell(vertex);
    this.graph.scrollCellToVisible(vertex);
    this.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX, 'vertex', vertex));
  }

  return vertex;
};

/**
 * Function: destroy
 *
 * Removes the editor and all its associated resources. This does not
 * normally need to be called, it is called automatically when the window
 * unloads.
 */
mxEditor.prototype.destroy = function () {
  if (!this.destroyed) {
    this.destroyed = true;

    if (this.tasks != null) {
      this.tasks.destroy();
    }

    if (this.outline != null) {
      this.outline.destroy();
    }

    if (this.properties != null) {
      this.properties.destroy();
    }

    if (this.keyHandler != null) {
      this.keyHandler.destroy();
    }

    if (this.rubberband != null) {
      this.rubberband.destroy();
    }

    if (this.toolbar != null) {
      this.toolbar.destroy();
    }

    if (this.graph != null) {
      this.graph.destroy();
    }

    this.status = null;
    this.templates = null;
  }
};

/**
 * io/mxCodecRegistry.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxCodecRegistry = {
  /**
   * Class: mxCodecRegistry
   *
   * Singleton class that acts as a global registry for codecs.
   *
   * Adding an <mxCodec>:
   *
   * 1. Define a default codec with a new instance of the
   * object to be handled.
   *
   * (code)
   * var codec = new mxObjectCodec(new mxGraphModel());
   * (end)
   *
   * 2. Define the functions required for encoding and decoding
   * objects.
   *
   * (code)
   * codec.encode = function(enc, obj) { ... }
   * codec.decode = function(dec, node, into) { ... }
   * (end)
   *
   * 3. Register the codec in the <mxCodecRegistry>.
   *
   * (code)
   * mxCodecRegistry.register(codec);
   * (end)
   *
   * <mxObjectCodec.decode> may be used to either create a new
   * instance of an object or to configure an existing instance,
   * in which case the into argument points to the existing
   * object. In this case, we say the codec "configures" the
   * object.
   *
   * Variable: codecs
   *
   * Maps from constructor names to codecs.
   */
  codecs: [],

  /**
   * Variable: aliases
   *
   * Maps from classnames to codecnames.
   */
  aliases: [],

  /**
   * Function: register
   *
   * Registers a new codec and associates the name of the template
   * constructor in the codec with the codec object.
   *
   * Parameters:
   *
   * codec - <mxObjectCodec> to be registered.
   */
  register: function (codec) {
    if (codec != null) {
      var name = codec.getName();
      mxCodecRegistry.codecs[name] = codec;

      var classname = mxUtils.getFunctionName(codec.template.constructor);

      if (classname != name) {
        mxCodecRegistry.addAlias(classname, name);
      }
    }

    return codec;
  },

  /**
   * Function: addAlias
   *
   * Adds an alias for mapping a classname to a codecname.
   */
  addAlias: function (classname, codecname) {
    mxCodecRegistry.aliases[classname] = codecname;
  },

  /**
   * Function: getCodec
   *
   * Returns a codec that handles objects that are constructed
   * using the given constructor.
   *
   * Parameters:
   *
   * ctor - JavaScript constructor function.
   */
  getCodec: function (ctor) {
    var codec = null;

    if (ctor != null) {
      var name = mxUtils.getFunctionName(ctor);
      var tmp = mxCodecRegistry.aliases[name];

      if (tmp != null) {
        name = tmp;
      }

      codec = mxCodecRegistry.codecs[name];

      // Registers a new default codec for the given constructor
      // if no codec has been previously defined.
      if (codec == null) {
        try {
          codec = new mxObjectCodec(new ctor());
          mxCodecRegistry.register(codec);
        } catch (e) {
          // ignore
        }
      }
    }

    return codec;
  },
};

/**
 * io/mxCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxCodec
 *
 * XML codec for JavaScript object graphs. See <mxObjectCodec> for a
 * description of the general encoding/decoding scheme. This class uses the
 * codecs registered in <mxCodecRegistry> for encoding/decoding each object.
 *
 * References:
 *
 * In order to resolve references, especially forward references, the mxCodec
 * constructor must be given the document that contains the referenced
 * elements.
 *
 * Examples:
 *
 * The following code is used to encode a graph model.
 *
 * (code)
 * var encoder = new mxCodec();
 * var result = encoder.encode(graph.getModel());
 * var xml = mxUtils.getXml(result);
 * (end)
 *
 * Example:
 *
 * Using the code below, an XML document is decoded into an existing model. The
 * document may be obtained using one of the functions in mxUtils for loading
 * an XML file, eg. <mxUtils.get>, or using <mxUtils.parseXml> for parsing an
 * XML string.
 *
 * (code)
 * var doc = mxUtils.parseXml(xmlString);
 * var codec = new mxCodec(doc);
 * codec.decode(doc.documentElement, graph.getModel());
 * (end)
 *
 * Example:
 *
 * This example demonstrates parsing a list of isolated cells into an existing
 * graph model. Note that the cells do not have a parent reference so they can
 * be added anywhere in the cell hierarchy after parsing.
 *
 * (code)
 * var xml = '<root><mxCell id="2" value="Hello," vertex="1"><mxGeometry x="20" y="20" width="80" height="30" as="geometry"/></mxCell><mxCell id="3" value="World!" vertex="1"><mxGeometry x="200" y="150" width="80" height="30" as="geometry"/></mxCell><mxCell id="4" value="" edge="1" source="2" target="3"><mxGeometry relative="1" as="geometry"/></mxCell></root>';
 * var doc = mxUtils.parseXml(xml);
 * var codec = new mxCodec(doc);
 * var elt = doc.documentElement.firstChild;
 * var cells = [];
 *
 * while (elt != null)
 * {
 *   cells.push(codec.decode(elt));
 *   elt = elt.nextSibling;
 * }
 *
 * graph.addCells(cells);
 * (end)
 *
 * Example:
 *
 * Using the following code, the selection cells of a graph are encoded and the
 * output is displayed in a dialog box.
 *
 * (code)
 * var enc = new mxCodec();
 * var cells = graph.getSelectionCells();
 * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells)));
 * (end)
 *
 * Newlines in the XML can be converted to <br>, in which case a '<br>' argument
 * must be passed to <mxUtils.getXml> as the second argument.
 *
 * Debugging:
 *
 * For debugging I/O you can use the following code to get the sequence of
 * encoded objects:
 *
 * (code)
 * var oldEncode = mxCodec.prototype.encode;
 * mxCodec.prototype.encode = function(obj)
 * {
 *   mxLog.show();
 *   mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor));
 *
 *   return oldEncode.apply(this, arguments);
 * };
 * (end)
 *
 * Note that the I/O system adds object codecs for new object automatically. For
 * decoding those objects, the constructor should be written as follows:
 *
 * (code)
 * var MyObj = function(name)
 * {
 *   // ...
 * };
 * (end)
 *
 * Constructor: mxCodec
 *
 * Constructs an XML encoder/decoder for the specified
 * owner document.
 *
 * Parameters:
 *
 * document - Optional XML document that contains the data.
 * If no document is specified then a new document is created
 * using <mxUtils.createXmlDocument>.
 */
function mxCodec(document) {
  this.document = document || mxUtils.createXmlDocument();
  this.objects = [];
}

/**
 * Variable: document
 *
 * The owner document of the codec.
 */
mxCodec.prototype.document = null;

/**
 * Variable: objects
 *
 * Maps from IDs to objects.
 */
mxCodec.prototype.objects = null;

/**
 * Variable: elements
 *
 * Lookup table for resolving IDs to elements.
 */
mxCodec.prototype.elements = null;

/**
 * Variable: encodeDefaults
 *
 * Specifies if default values should be encoded. Default is false.
 */
mxCodec.prototype.encodeDefaults = false;

/**
 * Function: putObject
 *
 * Assoiates the given object with the given ID and returns the given object.
 *
 * Parameters
 *
 * id - ID for the object to be associated with.
 * obj - Object to be associated with the ID.
 */
mxCodec.prototype.putObject = function (id, obj) {
  this.objects[id] = obj;

  return obj;
};

/**
 * Function: getObject
 *
 * Returns the decoded object for the element with the specified ID in
 * <document>. If the object is not known then <lookup> is used to find an
 * object. If no object is found, then the element with the respective ID
 * from the document is parsed using <decode>.
 */
mxCodec.prototype.getObject = function (id) {
  var obj = null;

  if (id != null) {
    obj = this.objects[id];

    if (obj == null) {
      obj = this.lookup(id);

      if (obj == null) {
        var node = this.getElementById(id);

        if (node != null) {
          obj = this.decode(node);
        }
      }
    }
  }

  return obj;
};

/**
 * Function: lookup
 *
 * Hook for subclassers to implement a custom lookup mechanism for cell IDs.
 * This implementation always returns null.
 *
 * Example:
 *
 * (code)
 * var codec = new mxCodec();
 * codec.lookup = function(id)
 * {
 *   return model.getCell(id);
 * };
 * (end)
 *
 * Parameters:
 *
 * id - ID of the object to be returned.
 */
mxCodec.prototype.lookup = function (id) {
  return null;
};

/**
 * Function: getElementById
 *
 * Returns the element with the given ID from <document>.
 *
 * Parameters:
 *
 * id - String that contains the ID.
 */
mxCodec.prototype.getElementById = function (id) {
  this.updateElements();

  return this.elements[id];
};

/**
 * Function: updateElements
 *
 * Returns the element with the given ID from <document>.
 *
 * Parameters:
 *
 * id - String that contains the ID.
 */
mxCodec.prototype.updateElements = function () {
  if (this.elements == null) {
    this.elements = new Object();

    if (this.document.documentElement != null) {
      this.addElement(this.document.documentElement);
    }
  }
};

/**
 * Function: addElement
 *
 * Adds the given element to <elements> if it has an ID.
 */
mxCodec.prototype.addElement = function (node) {
  if (node.nodeType == mxConstants.NODETYPE_ELEMENT) {
    var id = node.getAttribute('id');

    if (id != null) {
      if (this.elements[id] == null) {
        this.elements[id] = node;
      } else if (this.elements[id] != node) {
        throw new Error(id + ': Duplicate ID');
      }
    }
  }

  node = node.firstChild;

  while (node != null) {
    this.addElement(node);
    node = node.nextSibling;
  }
};

/**
 * Function: getId
 *
 * Returns the ID of the specified object. This implementation
 * calls <reference> first and if that returns null handles
 * the object as an <mxCell> by returning their IDs using
 * <mxCell.getId>. If no ID exists for the given cell, then
 * an on-the-fly ID is generated using <mxCellPath.create>.
 *
 * Parameters:
 *
 * obj - Object to return the ID for.
 */
mxCodec.prototype.getId = function (obj) {
  var id = null;

  if (obj != null) {
    id = this.reference(obj);

    if (id == null && obj instanceof mxCell) {
      id = obj.getId();

      if (id == null) {
        // Uses an on-the-fly Id
        id = mxCellPath.create(obj);

        if (id.length == 0) {
          id = 'root';
        }
      }
    }
  }

  return id;
};

/**
 * Function: reference
 *
 * Hook for subclassers to implement a custom method
 * for retrieving IDs from objects. This implementation
 * always returns null.
 *
 * Example:
 *
 * (code)
 * var codec = new mxCodec();
 * codec.reference = function(obj)
 * {
 *   return obj.getCustomId();
 * };
 * (end)
 *
 * Parameters:
 *
 * obj - Object whose ID should be returned.
 */
mxCodec.prototype.reference = function (obj) {
  return null;
};

/**
 * Function: encode
 *
 * Encodes the specified object and returns the resulting
 * XML node.
 *
 * Parameters:
 *
 * obj - Object to be encoded.
 */
mxCodec.prototype.encode = function (obj) {
  var node = null;

  if (obj != null && obj.constructor != null) {
    var enc = mxCodecRegistry.getCodec(obj.constructor);

    if (enc != null) {
      node = enc.encode(this, obj);
    } else {
      if (mxUtils.isNode(obj)) {
        node = mxUtils.importNode(this.document, obj, true);
      } else {
        mxLog.warn('mxCodec.encode: No codec for ' + mxUtils.getFunctionName(obj.constructor));
      }
    }
  }

  return node;
};

/**
 * Function: decode
 *
 * Decodes the given XML node. The optional "into"
 * argument specifies an existing object to be
 * used. If no object is given, then a new instance
 * is created using the constructor from the codec.
 *
 * The function returns the passed in object or
 * the new instance if no object was given.
 *
 * Parameters:
 *
 * node - XML node to be decoded.
 * into - Optional object to be decodec into.
 */
mxCodec.prototype.decode = function (node, into) {
  this.updateElements();
  var obj = null;

  if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT) {
    var ctor = null;

    try {
      ctor = window[node.nodeName];
    } catch (err) {
      // ignore
    }

    var dec = mxCodecRegistry.getCodec(ctor);

    if (dec != null) {
      obj = dec.decode(this, node, into);
    } else {
      obj = node.cloneNode(true);
      obj.removeAttribute('as');
    }
  }

  return obj;
};

/**
 * Function: encodeCell
 *
 * Encoding of cell hierarchies is built-into the core, but
 * is a higher-level function that needs to be explicitely
 * used by the respective object encoders (eg. <mxModelCodec>,
 * <mxChildChangeCodec> and <mxRootChangeCodec>). This
 * implementation writes the given cell and its children as a
 * (flat) sequence into the given node. The children are not
 * encoded if the optional includeChildren is false. The
 * function is in charge of adding the result into the
 * given node and has no return value.
 *
 * Parameters:
 *
 * cell - <mxCell> to be encoded.
 * node - Parent XML node to add the encoded cell into.
 * includeChildren - Optional boolean indicating if the
 * function should include all descendents. Default is true.
 */
mxCodec.prototype.encodeCell = function (cell, node, includeChildren) {
  node.appendChild(this.encode(cell));

  if (includeChildren == null || includeChildren) {
    var childCount = cell.getChildCount();

    for (var i = 0; i < childCount; i++) {
      this.encodeCell(cell.getChildAt(i), node);
    }
  }
};

/**
 * Function: isCellCodec
 *
 * Returns true if the given codec is a cell codec. This uses
 * <mxCellCodec.isCellCodec> to check if the codec is of the
 * given type.
 */
mxCodec.prototype.isCellCodec = function (codec) {
  if (codec != null && typeof codec.isCellCodec == 'function') {
    return codec.isCellCodec();
  }

  return false;
};

/**
 * Function: decodeCell
 *
 * Decodes cells that have been encoded using inversion, ie.
 * where the user object is the enclosing node in the XML,
 * and restores the group and graph structure in the cells.
 * Returns a new <mxCell> instance that represents the
 * given node.
 *
 * Parameters:
 *
 * node - XML node that contains the cell data.
 * restoreStructures - Optional boolean indicating whether
 * the graph structure should be restored by calling insert
 * and insertEdge on the parent and terminals, respectively.
 * Default is true.
 */
mxCodec.prototype.decodeCell = function (node, restoreStructures) {
  restoreStructures = restoreStructures != null ? restoreStructures : true;
  var cell = null;

  if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT) {
    // Tries to find a codec for the given node name. If that does
    // not return a codec then the node is the user object (an XML node
    // that contains the mxCell, aka inversion).
    var decoder = mxCodecRegistry.getCodec(node.nodeName);

    // Tries to find the codec for the cell inside the user object.
    // This assumes all node names inside the user object are either
    // not registered or they correspond to a class for cells.
    if (!this.isCellCodec(decoder)) {
      var child = node.firstChild;

      while (child != null && !this.isCellCodec(decoder)) {
        decoder = mxCodecRegistry.getCodec(child.nodeName);
        child = child.nextSibling;
      }
    }

    if (!this.isCellCodec(decoder)) {
      decoder = mxCodecRegistry.getCodec(mxCell);
    }

    cell = decoder.decode(this, node);

    if (restoreStructures) {
      this.insertIntoGraph(cell);
    }
  }

  return cell;
};

/**
 * Function: insertIntoGraph
 *
 * Inserts the given cell into its parent and terminal cells.
 */
mxCodec.prototype.insertIntoGraph = function (cell) {
  var parent = cell.parent;
  var source = cell.getTerminal(true);
  var target = cell.getTerminal(false);

  // Fixes possible inconsistencies during insert into graph
  cell.setTerminal(null, false);
  cell.setTerminal(null, true);
  cell.parent = null;

  if (parent != null) {
    if (parent == cell) {
      throw new Error(parent.id + ': Self Reference');
    } else {
      parent.insert(cell);
    }
  }

  if (source != null) {
    source.insertEdge(cell, true);
  }

  if (target != null) {
    target.insertEdge(cell, false);
  }
};

/**
 * Function: setAttribute
 *
 * Sets the attribute on the specified node to value. This is a
 * helper method that makes sure the attribute and value arguments
 * are not null.
 *
 * Parameters:
 *
 * node - XML node to set the attribute for.
 * attributes - Attributename to be set.
 * value - New value of the attribute.
 */
mxCodec.prototype.setAttribute = function (node, attribute, value) {
  if (attribute != null && value != null) {
    node.setAttribute(attribute, value);
  }
};

/**
 * io/mxObjectCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxObjectCodec
 *
 * Generic codec for JavaScript objects that implements a mapping between
 * JavaScript objects and XML nodes that maps each field or element to an
 * attribute or child node, and vice versa.
 *
 * Atomic Values:
 *
 * Consider the following example.
 *
 * (code)
 * var obj = new Object();
 * obj.foo = "Foo";
 * obj.bar = "Bar";
 * (end)
 *
 * This object is encoded into an XML node using the following.
 *
 * (code)
 * var enc = new mxCodec();
 * var node = enc.encode(obj);
 * (end)
 *
 * The output of the encoding may be viewed using <mxLog> as follows.
 *
 * (code)
 * mxLog.show();
 * mxLog.debug(mxUtils.getPrettyXml(node));
 * (end)
 *
 * Finally, the result of the encoding looks as follows.
 *
 * (code)
 * <Object foo="Foo" bar="Bar"/>
 * (end)
 *
 * In the above output, the foo and bar fields have been mapped to attributes
 * with the same names, and the name of the constructor was used for the
 * nodename.
 *
 * Booleans:
 *
 * Since booleans are numbers in JavaScript, all boolean values are encoded
 * into 1 for true and 0 for false. The decoder also accepts the string true
 * and false for boolean values.
 *
 * Objects:
 *
 * The above scheme is applied to all atomic fields, that is, to all non-object
 * fields of an object. For object fields, a child node is created with a
 * special attribute that contains the fieldname. This special attribute is
 * called "as" and hence, as is a reserved word that should not be used for a
 * fieldname.
 *
 * Consider the following example where foo is an object and bar is an atomic
 * property of foo.
 *
 * (code)
 * var obj = {foo: {bar: "Bar"}};
 * (end)
 *
 * This will be mapped to the following XML structure by mxObjectCodec.
 *
 * (code)
 * <Object>
 *   <Object bar="Bar" as="foo"/>
 * </Object>
 * (end)
 *
 * In the above output, the inner Object node contains the as-attribute that
 * specifies the fieldname in the enclosing object. That is, the field foo was
 * mapped to a child node with an as-attribute that has the value foo.
 *
 * Arrays:
 *
 * Arrays are special objects that are either associative, in which case each
 * key, value pair is treated like a field where the key is the fieldname, or
 * they are a sequence of atomic values and objects, which is mapped to a
 * sequence of child nodes. For object elements, the above scheme is applied
 * without the use of the special as-attribute for creating each child. For
 * atomic elements, a special add-node is created with the value stored in the
 * value-attribute.
 *
 * For example, the following array contains one atomic value and one object
 * with a field called bar. Furthermore it contains two associative entries
 * called bar with an atomic value, and foo with an object value.
 *
 * (code)
 * var obj = ["Bar", {bar: "Bar"}];
 * obj["bar"] = "Bar";
 * obj["foo"] = {bar: "Bar"};
 * (end)
 *
 * This array is represented by the following XML nodes.
 *
 * (code)
 * <Array bar="Bar">
 *   <add value="Bar"/>
 *   <Object bar="Bar"/>
 *   <Object bar="Bar" as="foo"/>
 * </Array>
 * (end)
 *
 * The Array node name is the name of the constructor. The additional
 * as-attribute in the last child contains the key of the associative entry,
 * whereas the second last child is part of the array sequence and does not
 * have an as-attribute.
 *
 * References:
 *
 * Objects may be represented as child nodes or attributes with ID values,
 * which are used to lookup the object in a table within <mxCodec>. The
 * <isReference> function is in charge of deciding if a specific field should
 * be encoded as a reference or not. Its default implementation returns true if
 * the fieldname is in <idrefs>, an array of strings that is used to configure
 * the <mxObjectCodec>.
 *
 * Using this approach, the mapping does not guarantee that the referenced
 * object itself exists in the document. The fields that are encoded as
 * references must be carefully chosen to make sure all referenced objects
 * exist in the document, or may be resolved by some other means if necessary.
 *
 * For example, in the case of the graph model all cells are stored in a tree
 * whose root is referenced by the model's root field. A tree is a structure
 * that is well suited for an XML representation, however, the additional edges
 * in the graph model have a reference to a source and target cell, which are
 * also contained in the tree. To handle this case, the source and target cell
 * of an edge are treated as references, whereas the children are treated as
 * objects. Since all cells are contained in the tree and no edge references a
 * source or target outside the tree, this setup makes sure all referenced
 * objects are contained in the document.
 *
 * In the case of a tree structure we must further avoid infinite recursion by
 * ignoring the parent reference of each child. This is done by returning true
 * in <isExcluded>, whose default implementation uses the array of excluded
 * fieldnames passed to the mxObjectCodec constructor.
 *
 * References are only used for cells in mxGraph. For defining other
 * referencable object types, the codec must be able to work out the ID of an
 * object. This is done by implementing <mxCodec.reference>. For decoding a
 * reference, the XML node with the respective id-attribute is fetched from the
 * document, decoded, and stored in a lookup table for later reference. For
 * looking up external objects, <mxCodec.lookup> may be implemented.
 *
 * Expressions:
 *
 * For decoding JavaScript expressions, the add-node may be used with a text
 * content that contains the JavaScript expression. For example, the following
 * creates a field called foo in the enclosing object and assigns it the value
 * of <mxConstants.ALIGN_LEFT>.
 *
 * (code)
 * <Object>
 *   <add as="foo">mxConstants.ALIGN_LEFT</add>
 * </Object>
 * (end)
 *
 * The resulting object has a field called foo with the value "left". Its XML
 * representation looks as follows.
 *
 * (code)
 * <Object foo="left"/>
 * (end)
 *
 * This means the expression is evaluated at decoding time and the result of
 * the evaluation is stored in the respective field. Valid expressions are all
 * JavaScript expressions, including function definitions, which are mapped to
 * functions on the resulting object.
 *
 * Expressions are only evaluated if <allowEval> is true.
 *
 * Constructor: mxObjectCodec
 *
 * Constructs a new codec for the specified template object.
 * The variables in the optional exclude array are ignored by
 * the codec. Variables in the optional idrefs array are
 * turned into references in the XML. The optional mapping
 * may be used to map from variable names to XML attributes.
 * The argument is created as follows:
 *
 * (code)
 * var mapping = new Object();
 * mapping['variableName'] = 'attribute-name';
 * (end)
 *
 * Parameters:
 *
 * template - Prototypical instance of the object to be
 * encoded/decoded.
 * exclude - Optional array of fieldnames to be ignored.
 * idrefs - Optional array of fieldnames to be converted to/from
 * references.
 * mapping - Optional mapping from field- to attributenames.
 */
function mxObjectCodec(template, exclude, idrefs, mapping) {
  this.template = template;

  this.exclude = exclude != null ? exclude : [];
  this.idrefs = idrefs != null ? idrefs : [];
  this.mapping = mapping != null ? mapping : [];

  this.reverse = new Object();

  for (var i in this.mapping) {
    this.reverse[this.mapping[i]] = i;
  }
}

/**
 * Variable: allowEval
 *
 * Static global switch that specifies if expressions in arrays are allowed.
 * Default is false. NOTE: Enabling this carries a possible security risk.
 */
mxObjectCodec.allowEval = false;

/**
 * Variable: template
 *
 * Holds the template object associated with this codec.
 */
mxObjectCodec.prototype.template = null;

/**
 * Variable: exclude
 *
 * Array containing the variable names that should be
 * ignored by the codec.
 */
mxObjectCodec.prototype.exclude = null;

/**
 * Variable: idrefs
 *
 * Array containing the variable names that should be
 * turned into or converted from references. See
 * <mxCodec.getId> and <mxCodec.getObject>.
 */
mxObjectCodec.prototype.idrefs = null;

/**
 * Variable: mapping
 *
 * Maps from from fieldnames to XML attribute names.
 */
mxObjectCodec.prototype.mapping = null;

/**
 * Variable: reverse
 *
 * Maps from from XML attribute names to fieldnames.
 */
mxObjectCodec.prototype.reverse = null;

/**
 * Function: getName
 *
 * Returns the name used for the nodenames and lookup of the codec when
 * classes are encoded and nodes are decoded. For classes to work with
 * this the codec registry automatically adds an alias for the classname
 * if that is different than what this returns. The default implementation
 * returns the classname of the template class.
 */
mxObjectCodec.prototype.getName = function () {
  return mxUtils.getFunctionName(this.template.constructor);
};

/**
 * Function: cloneTemplate
 *
 * Returns a new instance of the template for this codec.
 */
mxObjectCodec.prototype.cloneTemplate = function () {
  return new this.template.constructor();
};

/**
 * Function: getFieldName
 *
 * Returns the fieldname for the given attributename.
 * Looks up the value in the <reverse> mapping or returns
 * the input if there is no reverse mapping for the
 * given name.
 */
mxObjectCodec.prototype.getFieldName = function (attributename) {
  if (attributename != null) {
    var mapped = this.reverse[attributename];

    if (mapped != null) {
      attributename = mapped;
    }
  }

  return attributename;
};

/**
 * Function: getAttributeName
 *
 * Returns the attributename for the given fieldname.
 * Looks up the value in the <mapping> or returns
 * the input if there is no mapping for the
 * given name.
 */
mxObjectCodec.prototype.getAttributeName = function (fieldname) {
  if (fieldname != null) {
    var mapped = this.mapping[fieldname];

    if (mapped != null) {
      fieldname = mapped;
    }
  }

  return fieldname;
};

/**
 * Function: isExcluded
 *
 * Returns true if the given attribute is to be ignored by the codec. This
 * implementation returns true if the given fieldname is in <exclude> or
 * if the fieldname equals <mxObjectIdentity.FIELD_NAME>.
 *
 * Parameters:
 *
 * obj - Object instance that contains the field.
 * attr - Fieldname of the field.
 * value - Value of the field.
 * write - Boolean indicating if the field is being encoded or decoded.
 * Write is true if the field is being encoded, else it is being decoded.
 */
mxObjectCodec.prototype.isExcluded = function (obj, attr, value, write) {
  return attr == mxObjectIdentity.FIELD_NAME || mxUtils.indexOf(this.exclude, attr) >= 0;
};

/**
 * Function: isReference
 *
 * Returns true if the given fieldname is to be treated
 * as a textual reference (ID). This implementation returns
 * true if the given fieldname is in <idrefs>.
 *
 * Parameters:
 *
 * obj - Object instance that contains the field.
 * attr - Fieldname of the field.
 * value - Value of the field.
 * write - Boolean indicating if the field is being encoded or decoded.
 * Write is true if the field is being encoded, else it is being decoded.
 */
mxObjectCodec.prototype.isReference = function (obj, attr, value, write) {
  return mxUtils.indexOf(this.idrefs, attr) >= 0;
};

/**
 * Function: encode
 *
 * Encodes the specified object and returns a node
 * representing then given object. Calls <beforeEncode>
 * after creating the node and <afterEncode> with the
 * resulting node after processing.
 *
 * Enc is a reference to the calling encoder. It is used
 * to encode complex objects and create references.
 *
 * This implementation encodes all variables of an
 * object according to the following rules:
 *
 * - If the variable name is in <exclude> then it is ignored.
 * - If the variable name is in <idrefs> then <mxCodec.getId>
 * is used to replace the object with its ID.
 * - The variable name is mapped using <mapping>.
 * - If obj is an array and the variable name is numeric
 * (ie. an index) then it is not encoded.
 * - If the value is an object, then the codec is used to
 * create a child node with the variable name encoded into
 * the "as" attribute.
 * - Else, if <encodeDefaults> is true or the value differs
 * from the template value, then ...
 * - ... if obj is not an array, then the value is mapped to
 * an attribute.
 * - ... else if obj is an array, the value is mapped to an
 * add child with a value attribute or a text child node,
 * if the value is a function.
 *
 * If no ID exists for a variable in <idrefs> or if an object
 * cannot be encoded, a warning is issued using <mxLog.warn>.
 *
 * Returns the resulting XML node that represents the given
 * object.
 *
 * Parameters:
 *
 * enc - <mxCodec> that controls the encoding process.
 * obj - Object to be encoded.
 */
mxObjectCodec.prototype.encode = function (enc, obj) {
  var node = enc.document.createElement(this.getName());

  obj = this.beforeEncode(enc, obj, node);
  this.encodeObject(enc, obj, node);

  return this.afterEncode(enc, obj, node);
};

/**
 * Function: encodeObject
 *
 * Encodes the value of each member in then given obj into the given node using
 * <encodeValue>.
 *
 * Parameters:
 *
 * enc - <mxCodec> that controls the encoding process.
 * obj - Object to be encoded.
 * node - XML node that contains the encoded object.
 */
mxObjectCodec.prototype.encodeObject = function (enc, obj, node) {
  enc.setAttribute(node, 'id', enc.getId(obj));

  for (var i in obj) {
    var name = i;
    var value = obj[name];

    if (value != null && !this.isExcluded(obj, name, value, true)) {
      if (mxUtils.isInteger(name)) {
        name = null;
      }

      this.encodeValue(enc, obj, name, value, node);
    }
  }
};

/**
 * Function: encodeValue
 *
 * Converts the given value according to the mappings
 * and id-refs in this codec and uses <writeAttribute>
 * to write the attribute into the given node.
 *
 * Parameters:
 *
 * enc - <mxCodec> that controls the encoding process.
 * obj - Object whose property is going to be encoded.
 * name - XML node that contains the encoded object.
 * value - Value of the property to be encoded.
 * node - XML node that contains the encoded object.
 */
mxObjectCodec.prototype.encodeValue = function (enc, obj, name, value, node) {
  if (value != null) {
    if (this.isReference(obj, name, value, true)) {
      var tmp = enc.getId(value);

      if (tmp == null) {
        mxLog.warn('mxObjectCodec.encode: No ID for ' + this.getName() + '.' + name + '=' + value);
        return; // exit
      }

      value = tmp;
    }

    var defaultValue = this.template[name];

    // Checks if the value is a default value and
    // the name is correct
    if (name == null || enc.encodeDefaults || defaultValue != value) {
      name = this.getAttributeName(name);
      this.writeAttribute(enc, obj, name, value, node);
    }
  }
};

/**
 * Function: writeAttribute
 *
 * Writes the given value into node using <writePrimitiveAttribute>
 * or <writeComplexAttribute> depending on the type of the value.
 */
mxObjectCodec.prototype.writeAttribute = function (enc, obj, name, value, node) {
  if (typeof value != 'object' /* primitive type */) {
    this.writePrimitiveAttribute(enc, obj, name, value, node);
  } /* complex type */ else {
    this.writeComplexAttribute(enc, obj, name, value, node);
  }
};

/**
 * Function: writePrimitiveAttribute
 *
 * Writes the given value as an attribute of the given node.
 */
mxObjectCodec.prototype.writePrimitiveAttribute = function (enc, obj, name, value, node) {
  value = this.convertAttributeToXml(enc, obj, name, value, node);

  if (name == null) {
    var child = enc.document.createElement('add');

    if (typeof value == 'function') {
      child.appendChild(enc.document.createTextNode(value));
    } else {
      enc.setAttribute(child, 'value', value);
    }

    node.appendChild(child);
  } else if (typeof value != 'function') {
    enc.setAttribute(node, name, value);
  }
};

/**
 * Function: writeComplexAttribute
 *
 * Writes the given value as a child node of the given node.
 */
mxObjectCodec.prototype.writeComplexAttribute = function (enc, obj, name, value, node) {
  var child = enc.encode(value);

  if (child != null) {
    if (name != null) {
      child.setAttribute('as', name);
    }

    node.appendChild(child);
  } else {
    mxLog.warn('mxObjectCodec.encode: No node for ' + this.getName() + '.' + name + ': ' + value);
  }
};

/**
 * Function: convertAttributeToXml
 *
 * Converts true to "1" and false to "0" is <isBooleanAttribute> returns true.
 * All other values are not converted.
 *
 * Parameters:
 *
 * enc - <mxCodec> that controls the encoding process.
 * obj - Objec to convert the attribute for.
 * name - Name of the attribute to be converted.
 * value - Value to be converted.
 */
mxObjectCodec.prototype.convertAttributeToXml = function (enc, obj, name, value) {
  // Makes sure to encode boolean values as numeric values
  if (this.isBooleanAttribute(enc, obj, name, value)) {
    // Checks if the value is true (do not use the value as is, because
    // this would check if the value is not null, so 0 would be true)
    value = value == true ? '1' : '0';
  }

  return value;
};

/**
 * Function: isBooleanAttribute
 *
 * Returns true if the given object attribute is a boolean value.
 *
 * Parameters:
 *
 * enc - <mxCodec> that controls the encoding process.
 * obj - Objec to convert the attribute for.
 * name - Name of the attribute to be converted.
 * value - Value of the attribute to be converted.
 */
mxObjectCodec.prototype.isBooleanAttribute = function (enc, obj, name, value) {
  return typeof value.length == 'undefined' && (value == true || value == false);
};

/**
 * Function: convertAttributeFromXml
 *
 * Converts booleans and numeric values to the respective types. Values are
 * numeric if <isNumericAttribute> returns true.
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the decoding process.
 * attr - XML attribute to be converted.
 * obj - Objec to convert the attribute for.
 */
mxObjectCodec.prototype.convertAttributeFromXml = function (dec, attr, obj) {
  var value = attr.value;

  if (this.isNumericAttribute(dec, attr, obj)) {
    value = parseFloat(value);

    if (isNaN(value) || !isFinite(value)) {
      value = 0;
    }
  }

  return value;
};

/**
 * Function: isNumericAttribute
 *
 * Returns true if the given XML attribute is or should be a numeric value.
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the decoding process.
 * attr - XML attribute to be converted.
 * obj - Objec to convert the attribute for.
 */
mxObjectCodec.prototype.isNumericAttribute = function (dec, attr, obj) {
  // Handles known numeric attributes for generic objects
  var result =
    (obj.constructor == mxGeometry &&
      (attr.name == 'x' || attr.name == 'y' || attr.name == 'width' || attr.name == 'height')) ||
    (obj.constructor == mxPoint && (attr.name == 'x' || attr.name == 'y')) ||
    mxUtils.isNumeric(attr.value);

  return result;
};

/**
 * Function: beforeEncode
 *
 * Hook for subclassers to pre-process the object before
 * encoding. This returns the input object. The return
 * value of this function is used in <encode> to perform
 * the default encoding into the given node.
 *
 * Parameters:
 *
 * enc - <mxCodec> that controls the encoding process.
 * obj - Object to be encoded.
 * node - XML node to encode the object into.
 */
mxObjectCodec.prototype.beforeEncode = function (enc, obj, node) {
  return obj;
};

/**
 * Function: afterEncode
 *
 * Hook for subclassers to post-process the node
 * for the given object after encoding and return the
 * post-processed node. This implementation returns
 * the input node. The return value of this method
 * is returned to the encoder from <encode>.
 *
 * Parameters:
 *
 * enc - <mxCodec> that controls the encoding process.
 * obj - Object to be encoded.
 * node - XML node that represents the default encoding.
 */
mxObjectCodec.prototype.afterEncode = function (enc, obj, node) {
  return node;
};

/**
 * Function: decode
 *
 * Parses the given node into the object or returns a new object
 * representing the given node.
 *
 * Dec is a reference to the calling decoder. It is used to decode
 * complex objects and resolve references.
 *
 * If a node has an id attribute then the object cache is checked for the
 * object. If the object is not yet in the cache then it is constructed
 * using the constructor of <template> and cached in <mxCodec.objects>.
 *
 * This implementation decodes all attributes and childs of a node
 * according to the following rules:
 *
 * - If the variable name is in <exclude> or if the attribute name is "id"
 * or "as" then it is ignored.
 * - If the variable name is in <idrefs> then <mxCodec.getObject> is used
 * to replace the reference with an object.
 * - The variable name is mapped using a reverse <mapping>.
 * - If the value has a child node, then the codec is used to create a
 * child object with the variable name taken from the "as" attribute.
 * - If the object is an array and the variable name is empty then the
 * value or child object is appended to the array.
 * - If an add child has no value or the object is not an array then
 * the child text content is evaluated using <mxUtils.eval>.
 *
 * For add nodes where the object is not an array and the variable name
 * is defined, the default mechanism is used, allowing to override/add
 * methods as follows:
 *
 * (code)
 * <Object>
 *   <add as="hello"><![CDATA[
 *     function(arg1) {
 *       mxUtils.alert('Hello '+arg1);
 *     }
 *   ]]></add>
 * </Object>
 * (end)
 *
 * If no object exists for an ID in <idrefs> a warning is issued
 * using <mxLog.warn>.
 *
 * Returns the resulting object that represents the given XML node
 * or the object given to the method as the into parameter.
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the decoding process.
 * node - XML node to be decoded.
 * into - Optional objec to encode the node into.
 */
mxObjectCodec.prototype.decode = function (dec, node, into) {
  var id = node.getAttribute('id');
  var obj = dec.objects[id];

  if (obj == null) {
    obj = into || this.cloneTemplate();

    if (id != null) {
      dec.putObject(id, obj);
    }
  }

  node = this.beforeDecode(dec, node, obj);
  this.decodeNode(dec, node, obj);

  return this.afterDecode(dec, node, obj);
};

/**
 * Function: decodeNode
 *
 * Calls <decodeAttributes> and <decodeChildren> for the given node.
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the decoding process.
 * node - XML node to be decoded.
 * obj - Objec to encode the node into.
 */
mxObjectCodec.prototype.decodeNode = function (dec, node, obj) {
  if (node != null) {
    this.decodeAttributes(dec, node, obj);
    this.decodeChildren(dec, node, obj);
  }
};

/**
 * Function: decodeAttributes
 *
 * Decodes all attributes of the given node using <decodeAttribute>.
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the decoding process.
 * node - XML node to be decoded.
 * obj - Objec to encode the node into.
 */
mxObjectCodec.prototype.decodeAttributes = function (dec, node, obj) {
  var attrs = node.attributes;

  if (attrs != null) {
    for (var i = 0; i < attrs.length; i++) {
      this.decodeAttribute(dec, attrs[i], obj);
    }
  }
};

/**
 * Function: isIgnoredAttribute
 *
 * Returns true if the given attribute should be ignored. This implementation
 * returns true if the attribute name is "as" or "id".
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the decoding process.
 * attr - XML attribute to be decoded.
 * obj - Objec to encode the attribute into.
 */
mxObjectCodec.prototype.isIgnoredAttribute = function (dec, attr, obj) {
  return attr.nodeName == 'as' || attr.nodeName == 'id';
};

/**
 * Function: decodeAttribute
 *
 * Reads the given attribute into the specified object.
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the decoding process.
 * attr - XML attribute to be decoded.
 * obj - Objec to encode the attribute into.
 */
mxObjectCodec.prototype.decodeAttribute = function (dec, attr, obj) {
  if (!this.isIgnoredAttribute(dec, attr, obj)) {
    var name = attr.nodeName;

    // Converts the string true and false to their boolean values.
    // This may require an additional check on the obj to see if
    // the existing field is a boolean value or uninitialized, in
    // which case we may want to convert true and false to a string.
    var value = this.convertAttributeFromXml(dec, attr, obj);
    var fieldname = this.getFieldName(name);

    if (this.isReference(obj, fieldname, value, false)) {
      var tmp = dec.getObject(value);

      if (tmp == null) {
        mxLog.warn('mxObjectCodec.decode: No object for ' + this.getName() + '.' + name + '=' + value);
        return; // exit
      }

      value = tmp;
    }

    if (!this.isExcluded(obj, name, value, false)) {
      //mxLog.debug(mxUtils.getFunctionName(obj.constructor)+'.'+name+'='+value);
      obj[name] = value;
    }
  }
};

/**
 * Function: decodeChildren
 *
 * Decodes all children of the given node using <decodeChild>.
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the decoding process.
 * node - XML node to be decoded.
 * obj - Objec to encode the node into.
 */
mxObjectCodec.prototype.decodeChildren = function (dec, node, obj) {
  var child = node.firstChild;

  while (child != null) {
    var tmp = child.nextSibling;

    if (child.nodeType == mxConstants.NODETYPE_ELEMENT && !this.processInclude(dec, child, obj)) {
      this.decodeChild(dec, child, obj);
    }

    child = tmp;
  }
};

/**
 * Function: decodeChild
 *
 * Reads the specified child into the given object.
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the decoding process.
 * child - XML child element to be decoded.
 * obj - Objec to encode the node into.
 */
mxObjectCodec.prototype.decodeChild = function (dec, child, obj) {
  var fieldname = this.getFieldName(child.getAttribute('as'));

  if (fieldname == null || !this.isExcluded(obj, fieldname, child, false)) {
    var template = this.getFieldTemplate(obj, fieldname, child);
    var value = null;

    if (child.nodeName == 'add') {
      value = child.getAttribute('value');

      if (value == null && mxObjectCodec.allowEval) {
        value = mxUtils.eval(mxUtils.getTextContent(child));
      }
    } else {
      value = dec.decode(child, template);
    }

    try {
      this.addObjectValue(obj, fieldname, value, template);
    } catch (e) {
      throw new Error(e.message + ' for ' + child.nodeName);
    }
  }
};

/**
 * Function: getFieldTemplate
 *
 * Returns the template instance for the given field. This returns the
 * value of the field, null if the value is an array or an empty collection
 * if the value is a collection. The value is then used to populate the
 * field for a new instance. For strongly typed languages it may be
 * required to override this to return the correct collection instance
 * based on the encoded child.
 */
mxObjectCodec.prototype.getFieldTemplate = function (obj, fieldname, child) {
  var template = obj[fieldname];

  // Non-empty arrays are replaced completely
  if (template instanceof Array && template.length > 0) {
    template = null;
  }

  return template;
};

/**
 * Function: addObjectValue
 *
 * Sets the decoded child node as a value of the given object. If the
 * object is a map, then the value is added with the given fieldname as a
 * key. If the fieldname is not empty, then setFieldValue is called or
 * else, if the object is a collection, the value is added to the
 * collection. For strongly typed languages it may be required to
 * override this with the correct code to add an entry to an object.
 */
mxObjectCodec.prototype.addObjectValue = function (obj, fieldname, value, template) {
  if (value != null && value != template) {
    if (fieldname != null && fieldname.length > 0) {
      obj[fieldname] = value;
    } else {
      obj.push(value);
    }
    //mxLog.debug('Decoded '+mxUtils.getFunctionName(obj.constructor)+'.'+fieldname+': '+value);
  }
};

/**
 * Function: processInclude
 *
 * Returns true if the given node is an include directive and
 * executes the include by decoding the XML document. Returns
 * false if the given node is not an include directive.
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the encoding/decoding process.
 * node - XML node to be checked.
 * into - Optional object to pass-thru to the codec.
 */
mxObjectCodec.prototype.processInclude = function (dec, node, into) {
  if (node.nodeName == 'include') {
    var name = node.getAttribute('name');

    if (name != null) {
      try {
        var xml = mxUtils.load(name).getDocumentElement();

        if (xml != null) {
          dec.decode(xml, into);
        }
      } catch (e) {
        // ignore
      }
    }

    return true;
  }

  return false;
};

/**
 * Function: beforeDecode
 *
 * Hook for subclassers to pre-process the node for
 * the specified object and return the node to be
 * used for further processing by <decode>.
 * The object is created based on the template in the
 * calling method and is never null. This implementation
 * returns the input node. The return value of this
 * function is used in <decode> to perform
 * the default decoding into the given object.
 *
 * Parameters:
 *
 * dec - <mxCodec> that controls the decoding process.
 * node - XML node to be decoded.
 * obj - Object to encode the node into.
 */
mxObjectCodec.prototype.beforeDecode = function (dec, node, obj) {
  return node;
};

/**
 * Function: afterDecode
 *
 * Hook for subclassers to post-process the object after
 * decoding. This implementation returns the given object
 * without any changes. The return value of this method
 * is returned to the decoder from <decode>.
 *
 * Parameters:
 *
 * enc - <mxCodec> that controls the encoding process.
 * node - XML node to be decoded.
 * obj - Object that represents the default decoding.
 */
mxObjectCodec.prototype.afterDecode = function (dec, node, obj) {
  return obj;
};

/**
 * io/mxCellCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxCellCodec
     *
     * Codec for <mxCell>s. This class is created and registered
     * dynamically at load time and used implicitly via <mxCodec>
     * and the <mxCodecRegistry>.
     *
     * Transient Fields:
     *
     * - children
     * - edges
     * - overlays
     * - mxTransient
     *
     * Reference Fields:
     *
     * - parent
     * - source
     * - target
     *
     * Transient fields can be added using the following code:
     *
     * mxCodecRegistry.getCodec(mxCell).exclude.push('name_of_field');
     *
     * To subclass <mxCell>, replace the template and add an alias as
     * follows.
     *
     * (code)
     * function CustomCell(value, geometry, style)
     * {
     *   mxCell.apply(this, arguments);
     * }
     *
     * mxUtils.extend(CustomCell, mxCell);
     *
     * mxCodecRegistry.getCodec(mxCell).template = new CustomCell();
     * mxCodecRegistry.addAlias('CustomCell', 'mxCell');
     * (end)
     */
    var codec = new mxObjectCodec(
      new mxCell(),
      ['children', 'edges', 'overlays', 'mxTransient'],
      ['parent', 'source', 'target']
    );

    /**
     * Function: isCellCodec
     *
     * Returns true since this is a cell codec.
     */
    codec.isCellCodec = function () {
      return true;
    };

    /**
     * Overidden to disable conversion of value to number.
     */
    codec.isNumericAttribute = function (dec, attr, obj) {
      return attr.nodeName !== 'value' && mxObjectCodec.prototype.isNumericAttribute.apply(this, arguments);
    };

    /**
     * Function: isExcluded
     *
     * Excludes user objects that are XML nodes.
     */
    codec.isExcluded = function (obj, attr, value, isWrite) {
      return (
        mxObjectCodec.prototype.isExcluded.apply(this, arguments) ||
        (isWrite && attr == 'value' && value.nodeType == mxConstants.NODETYPE_ELEMENT)
      );
    };

    /**
     * Function: afterEncode
     *
     * Encodes an <mxCell> and wraps the XML up inside the
     * XML of the user object (inversion).
     */
    codec.afterEncode = function (enc, obj, node) {
      if (obj.value != null && obj.value.nodeType == mxConstants.NODETYPE_ELEMENT) {
        // Wraps the graphical annotation up in the user object (inversion)
        // by putting the result of the default encoding into a clone of the
        // user object (node type 1) and returning this cloned user object.
        var tmp = node;
        node = mxUtils.importNode(enc.document, obj.value, true);
        node.appendChild(tmp);

        // Moves the id attribute to the outermost XML node, namely the
        // node which denotes the object boundaries in the file.
        var id = tmp.getAttribute('id');
        node.setAttribute('id', id);
        tmp.removeAttribute('id');
      }

      return node;
    };

    /**
     * Function: beforeDecode
     *
     * Decodes an <mxCell> and uses the enclosing XML node as
     * the user object for the cell (inversion).
     */
    codec.beforeDecode = function (dec, node, obj) {
      var inner = node.cloneNode(true);
      var classname = this.getName();

      if (node.nodeName != classname) {
        // Passes the inner graphical annotation node to the
        // object codec for further processing of the cell.
        var tmp = node.getElementsByTagName(classname)[0];

        if (tmp != null && tmp.parentNode == node) {
          mxUtils.removeWhitespace(tmp, true);
          mxUtils.removeWhitespace(tmp, false);
          tmp.parentNode.removeChild(tmp);
          inner = tmp;
        } else {
          inner = null;
        }

        // Creates the user object out of the XML node
        obj.value = node.cloneNode(true);
        var id = obj.value.getAttribute('id');

        if (id != null) {
          obj.setId(id);
          obj.value.removeAttribute('id');
        }
      } else {
        // Uses ID from XML file as ID for cell in model
        obj.setId(node.getAttribute('id'));
      }

      // Preprocesses and removes all Id-references in order to use the
      // correct encoder (this) for the known references to cells (all).
      if (inner != null) {
        for (var i = 0; i < this.idrefs.length; i++) {
          var attr = this.idrefs[i];
          var ref = inner.getAttribute(attr);

          if (ref != null) {
            inner.removeAttribute(attr);
            var object = dec.objects[ref] || dec.lookup(ref);

            if (object == null) {
              // Needs to decode forward reference
              var element = dec.getElementById(ref);

              if (element != null) {
                var decoder = mxCodecRegistry.codecs[element.nodeName] || this;
                object = decoder.decode(dec, element);
              }
            }

            obj[attr] = object;
          }
        }
      }

      return inner;
    };

    // Returns the codec into the registry
    return codec;
  })()
);

/**
 * io/mxModelCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxModelCodec
     *
     * Codec for <mxGraphModel>s. This class is created and registered
     * dynamically at load time and used implicitly via <mxCodec>
     * and the <mxCodecRegistry>.
     */
    var codec = new mxObjectCodec(new mxGraphModel());

    /**
     * Function: encodeObject
     *
     * Encodes the given <mxGraphModel> by writing a (flat) XML sequence of
     * cell nodes as produced by the <mxCellCodec>. The sequence is
     * wrapped-up in a node with the name root.
     */
    codec.encodeObject = function (enc, obj, node) {
      var rootNode = enc.document.createElement('root');
      enc.encodeCell(obj.getRoot(), rootNode);
      node.appendChild(rootNode);
    };

    /**
     * Function: decodeChild
     *
     * Overrides decode child to handle special child nodes.
     */
    codec.decodeChild = function (dec, child, obj) {
      if (child.nodeName == 'root') {
        this.decodeRoot(dec, child, obj);
      } else {
        mxObjectCodec.prototype.decodeChild.apply(this, arguments);
      }
    };

    /**
     * Function: decodeRoot
     *
     * Reads the cells into the graph model. All cells
     * are children of the root element in the node.
     */
    codec.decodeRoot = function (dec, root, model) {
      var rootCell = null;
      var tmp = root.firstChild;

      while (tmp != null) {
        var cell = dec.decodeCell(tmp);

        if (cell != null && cell.getParent() == null) {
          rootCell = cell;
        }

        tmp = tmp.nextSibling;
      }

      // Sets the root on the model if one has been decoded
      if (rootCell != null) {
        model.setRoot(rootCell);
      }
    };

    // Returns the codec into the registry
    return codec;
  })()
);

/**
 * io/mxRootChangeCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxRootChangeCodec
     *
     * Codec for <mxRootChange>s. This class is created and registered
     * dynamically at load time and used implicitly via <mxCodec> and
     * the <mxCodecRegistry>.
     *
     * Transient Fields:
     *
     * - model
     * - previous
     * - root
     */
    var codec = new mxObjectCodec(new mxRootChange(), ['model', 'previous', 'root']);

    /**
     * Function: onEncode
     *
     * Encodes the child recursively.
     */
    codec.afterEncode = function (enc, obj, node) {
      enc.encodeCell(obj.root, node);

      return node;
    };

    /**
     * Function: beforeDecode
     *
     * Decodes the optional children as cells
     * using the respective decoder.
     */
    codec.beforeDecode = function (dec, node, obj) {
      if (node.firstChild != null && node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT) {
        // Makes sure the original node isn't modified
        node = node.cloneNode(true);

        var tmp = node.firstChild;
        obj.root = dec.decodeCell(tmp, false);

        var tmp2 = tmp.nextSibling;
        tmp.parentNode.removeChild(tmp);
        tmp = tmp2;

        while (tmp != null) {
          tmp2 = tmp.nextSibling;
          dec.decodeCell(tmp);
          tmp.parentNode.removeChild(tmp);
          tmp = tmp2;
        }
      }

      return node;
    };

    /**
     * Function: afterDecode
     *
     * Restores the state by assigning the previous value.
     */
    codec.afterDecode = function (dec, node, obj) {
      obj.previous = obj.root;

      return obj;
    };

    // Returns the codec into the registry
    return codec;
  })()
);

/**
 * io/mxChildChangeCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxChildChangeCodec
     *
     * Codec for <mxChildChange>s. This class is created and registered
     * dynamically at load time and used implicitly via <mxCodec> and
     * the <mxCodecRegistry>.
     *
     * Transient Fields:
     *
     * - model
     * - previous
     * - previousIndex
     * - child
     *
     * Reference Fields:
     *
     * - parent
     */
    var codec = new mxObjectCodec(new mxChildChange(), ['model', 'child', 'previousIndex'], ['parent', 'previous']);

    /**
     * Function: isReference
     *
     * Returns true for the child attribute if the child
     * cell had a previous parent or if we're reading the
     * child as an attribute rather than a child node, in
     * which case it's always a reference.
     */
    codec.isReference = function (obj, attr, value, isWrite) {
      if (attr == 'child' && (!isWrite || obj.model.contains(obj.previous))) {
        return true;
      }

      return mxUtils.indexOf(this.idrefs, attr) >= 0;
    };

    /**
     * Function: isExcluded
     *
     * Excludes references to parent or previous if not in the model.
     */
    codec.isExcluded = function (obj, attr, value, write) {
      return (
        mxObjectCodec.prototype.isExcluded.apply(this, arguments) ||
        (write && value != null && (attr == 'previous' || attr == 'parent') && !obj.model.contains(value))
      );
    };

    /**
     * Function: afterEncode
     *
     * Encodes the child recusively and adds the result
     * to the given node.
     */
    codec.afterEncode = function (enc, obj, node) {
      if (this.isReference(obj, 'child', obj.child, true)) {
        // Encodes as reference (id)
        node.setAttribute('child', enc.getId(obj.child));
      } else {
        // At this point, the encoder is no longer able to know which cells
        // are new, so we have to encode the complete cell hierarchy and
        // ignore the ones that are already there at decoding time. Note:
        // This can only be resolved by moving the notify event into the
        // execute of the edit.
        enc.encodeCell(obj.child, node);
      }

      return node;
    };

    /**
     * Function: beforeDecode
     *
     * Decodes the any child nodes as using the respective
     * codec from the registry.
     */
    codec.beforeDecode = function (dec, node, obj) {
      if (node.firstChild != null && node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT) {
        // Makes sure the original node isn't modified
        node = node.cloneNode(true);

        var tmp = node.firstChild;
        obj.child = dec.decodeCell(tmp, false);

        var tmp2 = tmp.nextSibling;
        tmp.parentNode.removeChild(tmp);
        tmp = tmp2;

        while (tmp != null) {
          tmp2 = tmp.nextSibling;

          if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT) {
            // Ignores all existing cells because those do not need to
            // be re-inserted into the model. Since the encoded version
            // of these cells contains the new parent, this would leave
            // to an inconsistent state on the model (ie. a parent
            // change without a call to parentForCellChanged).
            var id = tmp.getAttribute('id');

            if (dec.lookup(id) == null) {
              dec.decodeCell(tmp);
            }
          }

          tmp.parentNode.removeChild(tmp);
          tmp = tmp2;
        }
      } else {
        var childRef = node.getAttribute('child');
        obj.child = dec.getObject(childRef);
      }

      return node;
    };

    /**
     * Function: afterDecode
     *
     * Restores object state in the child change.
     */
    codec.afterDecode = function (dec, node, obj) {
      // Cells are decoded here after a complete transaction so the previous
      // parent must be restored on the cell for the case where the cell was
      // added. This is needed for the local model to identify the cell as a
      // new cell and register the ID.
      if (obj.child != null) {
        if (obj.child.parent != null && obj.previous != null && obj.child.parent != obj.previous) {
          obj.previous = obj.child.parent;
        }

        obj.child.parent = obj.previous;
        obj.previous = obj.parent;
        obj.previousIndex = obj.index;
      }

      return obj;
    };

    // Returns the codec into the registry
    return codec;
  })()
);

/**
 * io/mxTerminalChangeCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxTerminalChangeCodec
     *
     * Codec for <mxTerminalChange>s. This class is created and registered
     * dynamically at load time and used implicitly via <mxCodec> and
     * the <mxCodecRegistry>.
     *
     * Transient Fields:
     *
     * - model
     * - previous
     *
     * Reference Fields:
     *
     * - cell
     * - terminal
     */
    var codec = new mxObjectCodec(new mxTerminalChange(), ['model', 'previous'], ['cell', 'terminal']);

    /**
     * Function: afterDecode
     *
     * Restores the state by assigning the previous value.
     */
    codec.afterDecode = function (dec, node, obj) {
      obj.previous = obj.terminal;

      return obj;
    };

    // Returns the codec into the registry
    return codec;
  })()
);

/**
 * io/mxGenericChangeCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGenericChangeCodec
 *
 * Codec for <mxValueChange>s, <mxStyleChange>s, <mxGeometryChange>s,
 * <mxCollapseChange>s and <mxVisibleChange>s. This class is created
 * and registered dynamically at load time and used implicitly
 * via <mxCodec> and the <mxCodecRegistry>.
 *
 * Transient Fields:
 *
 * - model
 * - previous
 *
 * Reference Fields:
 *
 * - cell
 *
 * Constructor: mxGenericChangeCodec
 *
 * Factory function that creates a <mxObjectCodec> for
 * the specified change and fieldname.
 *
 * Parameters:
 *
 * obj - An instance of the change object.
 * variable - The fieldname for the change data.
 */
var mxGenericChangeCodec = function (obj, variable) {
  var codec = new mxObjectCodec(obj, ['model', 'previous'], ['cell']);

  /**
   * Function: afterDecode
   *
   * Restores the state by assigning the previous value.
   */
  codec.afterDecode = function (dec, node, obj) {
    // Allows forward references in sessions. This is a workaround
    // for the sequence of edits in mxGraph.moveCells and cellsAdded.
    if (mxUtils.isNode(obj.cell)) {
      obj.cell = dec.decodeCell(obj.cell, false);
    }

    obj.previous = obj[variable];

    return obj;
  };

  return codec;
};

// Registers the codecs
mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange(), 'value'));
mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange(), 'style'));
mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange(), 'geometry'));
mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange(), 'collapsed'));
mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange(), 'visible'));
mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange(), 'value'));

/**
 * io/mxGraphCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxGraphCodec
     *
     * Codec for <mxGraph>s. This class is created and registered
     * dynamically at load time and used implicitly via <mxCodec>
     * and the <mxCodecRegistry>.
     *
     * Transient Fields:
     *
     * - graphListeners
     * - eventListeners
     * - view
     * - container
     * - cellRenderer
     * - editor
     * - selection
     */
    return new mxObjectCodec(new mxGraph(), [
      'graphListeners',
      'eventListeners',
      'view',
      'container',
      'cellRenderer',
      'editor',
      'selection',
    ]);
  })()
);

/**
 * io/mxGraphViewCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxGraphViewCodec
     *
     * Custom encoder for <mxGraphView>s. This class is created
     * and registered dynamically at load time and used implicitly via
     * <mxCodec> and the <mxCodecRegistry>. This codec only writes views
     * into a XML format that can be used to create an image for
     * the graph, that is, it contains absolute coordinates with
     * computed perimeters, edge styles and cell styles.
     */
    var codec = new mxObjectCodec(new mxGraphView());

    /**
     * Function: encode
     *
     * Encodes the given <mxGraphView> using <encodeCell>
     * starting at the model's root. This returns the
     * top-level graph node of the recursive encoding.
     */
    codec.encode = function (enc, view) {
      return this.encodeCell(enc, view, view.graph.getModel().getRoot());
    };

    /**
     * Function: encodeCell
     *
     * Recursively encodes the specifed cell. Uses layer
     * as the default nodename. If the cell's parent is
     * null, then graph is used for the nodename. If
     * <mxGraphModel.isEdge> returns true for the cell,
     * then edge is used for the nodename, else if
     * <mxGraphModel.isVertex> returns true for the cell,
     * then vertex is used for the nodename.
     *
     * <mxGraph.getLabel> is used to create the label
     * attribute for the cell. For graph nodes and vertices
     * the bounds are encoded into x, y, width and height.
     * For edges the points are encoded into a points
     * attribute as a space-separated list of comma-separated
     * coordinate pairs (eg. x0,y0 x1,y1 ... xn,yn). All
     * values from the cell style are added as attribute
     * values to the node.
     */
    codec.encodeCell = function (enc, view, cell) {
      var model = view.graph.getModel();
      var state = view.getState(cell);
      var parent = model.getParent(cell);

      if (parent == null || state != null) {
        var childCount = model.getChildCount(cell);
        var geo = view.graph.getCellGeometry(cell);
        var name = null;

        if (parent == model.getRoot()) {
          name = 'layer';
        } else if (parent == null) {
          name = 'graph';
        } else if (model.isEdge(cell)) {
          name = 'edge';
        } else if (childCount > 0 && geo != null) {
          name = 'group';
        } else if (model.isVertex(cell)) {
          name = 'vertex';
        }

        if (name != null) {
          var node = enc.document.createElement(name);
          var lab = view.graph.getLabel(cell);

          if (lab != null) {
            node.setAttribute('label', view.graph.getLabel(cell));

            if (view.graph.isHtmlLabel(cell)) {
              node.setAttribute('html', true);
            }
          }

          if (parent == null) {
            var bounds = view.getGraphBounds();

            if (bounds != null) {
              node.setAttribute('x', Math.round(bounds.x));
              node.setAttribute('y', Math.round(bounds.y));
              node.setAttribute('width', Math.round(bounds.width));
              node.setAttribute('height', Math.round(bounds.height));
            }

            node.setAttribute('scale', view.scale);
          } else if (state != null && geo != null) {
            // Writes each key, value in the style pair to an attribute
            for (var i in state.style) {
              var value = state.style[i];

              // Tries to turn objects and functions into strings
              if (typeof value == 'function' && typeof value == 'object') {
                value = mxStyleRegistry.getName(value);
              }

              if (value != null && typeof value != 'function' && typeof value != 'object') {
                node.setAttribute(i, value);
              }
            }

            var abs = state.absolutePoints;

            // Writes the list of points into one attribute
            if (abs != null && abs.length > 0) {
              var pts = Math.round(abs[0].x) + ',' + Math.round(abs[0].y);

              for (var i = 1; i < abs.length; i++) {
                pts += ' ' + Math.round(abs[i].x) + ',' + Math.round(abs[i].y);
              }

              node.setAttribute('points', pts);
            }

            // Writes the bounds into 4 attributes
            else {
              node.setAttribute('x', Math.round(state.x));
              node.setAttribute('y', Math.round(state.y));
              node.setAttribute('width', Math.round(state.width));
              node.setAttribute('height', Math.round(state.height));
            }

            var offset = state.absoluteOffset;

            // Writes the offset into 2 attributes
            if (offset != null) {
              if (offset.x != 0) {
                node.setAttribute('dx', Math.round(offset.x));
              }

              if (offset.y != 0) {
                node.setAttribute('dy', Math.round(offset.y));
              }
            }
          }

          for (var i = 0; i < childCount; i++) {
            var childNode = this.encodeCell(enc, view, model.getChildAt(cell, i));

            if (childNode != null) {
              node.appendChild(childNode);
            }
          }
        }
      }

      return node;
    };

    // Returns the codec into the registry
    return codec;
  })()
);

/**
 * io/mxStylesheetCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxStylesheetCodec
 *
 * Codec for <mxStylesheet>s. This class is created and registered
 * dynamically at load time and used implicitly via <mxCodec>
 * and the <mxCodecRegistry>.
 */
var mxStylesheetCodec = mxCodecRegistry.register(
  (function () {
    var codec = new mxObjectCodec(new mxStylesheet());

    /**
     * Function: encode
     *
     * Encodes a stylesheet. See <decode> for a description of the
     * format.
     */
    codec.encode = function (enc, obj) {
      var node = enc.document.createElement(this.getName());

      for (var i in obj.styles) {
        var style = obj.styles[i];
        var styleNode = enc.document.createElement('add');

        if (i != null) {
          styleNode.setAttribute('as', i);

          for (var j in style) {
            var value = this.getStringValue(j, style[j]);

            if (value != null) {
              var entry = enc.document.createElement('add');
              entry.setAttribute('value', value);
              entry.setAttribute('as', j);
              styleNode.appendChild(entry);
            }
          }

          if (styleNode.childNodes.length > 0) {
            node.appendChild(styleNode);
          }
        }
      }

      return node;
    };

    /**
     * Function: getStringValue
     *
     * Returns the string for encoding the given value.
     */
    codec.getStringValue = function (key, value) {
      var type = typeof value;

      if (type == 'function') {
        value = mxStyleRegistry.getName(value);
      } else if (type == 'object') {
        value = null;
      }

      return value;
    };

    /**
     * Function: decode
     *
     * Reads a sequence of the following child nodes
     * and attributes:
     *
     * Child Nodes:
     *
     * add - Adds a new style.
     *
     * Attributes:
     *
     * as - Name of the style.
     * extend - Name of the style to inherit from.
     *
     * Each node contains another sequence of add and remove nodes with the following
     * attributes:
     *
     * as - Name of the style (see <mxConstants>).
     * value - Value for the style.
     *
     * Instead of the value-attribute, one can put Javascript expressions into
     * the node as follows if <mxStylesheetCodec.allowEval> is true:
     * <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
     *
     * A remove node will remove the entry with the name given in the as-attribute
     * from the style.
     *
     * Example:
     *
     * (code)
     * <mxStylesheet as="stylesheet">
     *   <add as="text">
     *     <add as="fontSize" value="12"/>
     *   </add>
     *   <add as="defaultVertex" extend="text">
     *     <add as="shape" value="rectangle"/>
     *   </add>
     * </mxStylesheet>
     * (end)
     */
    codec.decode = function (dec, node, into) {
      var obj = into || new this.template.constructor();
      var id = node.getAttribute('id');

      if (id != null) {
        dec.objects[id] = obj;
      }

      node = node.firstChild;

      while (node != null) {
        if (!this.processInclude(dec, node, obj) && node.nodeName == 'add') {
          var as = node.getAttribute('as');

          if (as != null) {
            var extend = node.getAttribute('extend');
            var style = extend != null ? mxUtils.clone(obj.styles[extend]) : null;

            if (style == null) {
              if (extend != null) {
                mxLog.warn('mxStylesheetCodec.decode: stylesheet ' + extend + ' not found to extend');
              }

              style = new Object();
            }

            var entry = node.firstChild;

            while (entry != null) {
              if (entry.nodeType == mxConstants.NODETYPE_ELEMENT) {
                var key = entry.getAttribute('as');

                if (entry.nodeName == 'add') {
                  var text = mxUtils.getTextContent(entry);
                  var value = null;

                  if (text != null && text.length > 0 && mxStylesheetCodec.allowEval) {
                    value = mxUtils.eval(text);
                  } else {
                    value = entry.getAttribute('value');

                    if (mxUtils.isNumeric(value)) {
                      value = parseFloat(value);
                    }
                  }

                  if (value != null) {
                    style[key] = value;
                  }
                } else if (entry.nodeName == 'remove') {
                  delete style[key];
                }
              }

              entry = entry.nextSibling;
            }

            obj.putCellStyle(as, style);
          }
        }

        node = node.nextSibling;
      }

      return obj;
    };

    // Returns the codec into the registry
    return codec;
  })()
);

/**
 * Variable: allowEval
 *
 * Static global switch that specifies if the use of eval is allowed for
 * evaluating text content. Default is true. Set this to false if stylesheets
 * may contain user input.
 */
mxStylesheetCodec.allowEval = true;

/**
 * io/mxDefaultKeyHandlerCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxDefaultKeyHandlerCodec
     *
     * Custom codec for configuring <mxDefaultKeyHandler>s. This class is created
     * and registered dynamically at load time and used implicitly via
     * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
     * data for existing key handlers, it does not encode or create key handlers.
     */
    var codec = new mxObjectCodec(new mxDefaultKeyHandler());

    /**
     * Function: encode
     *
     * Returns null.
     */
    codec.encode = function (enc, obj) {
      return null;
    };

    /**
     * Function: decode
     *
     * Reads a sequence of the following child nodes
     * and attributes:
     *
     * Child Nodes:
     *
     * add - Binds a keystroke to an actionname.
     *
     * Attributes:
     *
     * as - Keycode.
     * action - Actionname to execute in editor.
     * control - Optional boolean indicating if
     * 		the control key must be pressed.
     *
     * Example:
     *
     * (code)
     * <mxDefaultKeyHandler as="keyHandler">
     *   <add as="88" control="true" action="cut"/>
     *   <add as="67" control="true" action="copy"/>
     *   <add as="86" control="true" action="paste"/>
     * </mxDefaultKeyHandler>
     * (end)
     *
     * The keycodes are for the x, c and v keys.
     *
     * See also: <mxDefaultKeyHandler.bindAction>,
     * http://www.js-examples.com/page/tutorials__key_codes.html
     */
    codec.decode = function (dec, node, into) {
      if (into != null) {
        var editor = into.editor;
        node = node.firstChild;

        while (node != null) {
          if (!this.processInclude(dec, node, into) && node.nodeName == 'add') {
            var as = node.getAttribute('as');
            var action = node.getAttribute('action');
            var control = node.getAttribute('control');

            into.bindAction(as, action, control);
          }

          node = node.nextSibling;
        }
      }

      return into;
    };

    // Returns the codec into the registry
    return codec;
  })()
);

/**
 * io/mxDefaultToolbarCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDefaultToolbarCodec
 *
 * Custom codec for configuring <mxDefaultToolbar>s. This class is created
 * and registered dynamically at load time and used implicitly via
 * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
 * data for existing toolbars handlers, it does not encode or create toolbars.
 */
var mxDefaultToolbarCodec = mxCodecRegistry.register(
  (function () {
    var codec = new mxObjectCodec(new mxDefaultToolbar());

    /**
     * Function: encode
     *
     * Returns null.
     */
    codec.encode = function (enc, obj) {
      return null;
    };

    /**
     * Function: decode
     *
     * Reads a sequence of the following child nodes
     * and attributes:
     *
     * Child Nodes:
     *
     * add - Adds a new item to the toolbar. See below for attributes.
     * separator - Adds a vertical separator. No attributes.
     * hr - Adds a horizontal separator. No attributes.
     * br - Adds a linefeed. No attributes.
     *
     * Attributes:
     *
     * as - Resource key for the label.
     * action - Name of the action to execute in enclosing editor.
     * mode - Modename (see below).
     * template - Template name for cell insertion.
     * style - Optional style to override the template style.
     * icon - Icon (relative/absolute URL).
     * pressedIcon - Optional icon for pressed state (relative/absolute URL).
     * id - Optional ID to be used for the created DOM element.
     * toggle - Optional 0 or 1 to disable toggling of the element. Default is
     * 1 (true).
     *
     * The action, mode and template attributes are mutually exclusive. The
     * style can only be used with the template attribute. The add node may
     * contain another sequence of add nodes with as and action attributes
     * to create a combo box in the toolbar. If the icon is specified then
     * a list of the child node is expected to have its template attribute
     * set and the action is ignored instead.
     *
     * Nodes with a specified template may define a function to be used for
     * inserting the cloned template into the graph. Here is an example of such
     * a node:
     *
     * (code)
     * <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"><![CDATA[
     *   function (editor, cell, evt, targetCell)
     *   {
     *     var pt = mxUtils.convertPoint(
     *       editor.graph.container, mxEvent.getClientX(evt),
     *         mxEvent.getClientY(evt));
     *     return editor.addVertex(targetCell, cell, pt.x, pt.y);
     *   }
     * ]]></add>
     * (end)
     *
     * In the above function, editor is the enclosing <mxEditor> instance, cell
     * is the clone of the template, evt is the mouse event that represents the
     * drop and targetCell is the cell under the mousepointer where the drop
     * occurred. The targetCell is retrieved using <mxGraph.getCellAt>.
     *
     * Futhermore, nodes with the mode attribute may define a function to
     * be executed upon selection of the respective toolbar icon. In the
     * example below, the default edge style is set when this specific
     * connect-mode is activated:
     *
     * (code)
     * <add as="connect" mode="connect"><![CDATA[
     *   function (editor)
     *   {
     *     if (editor.defaultEdge != null)
     *     {
     *       editor.defaultEdge.style = 'straightEdge';
     *     }
     *   }
     * ]]></add>
     * (end)
     *
     * Both functions require <mxDefaultToolbarCodec.allowEval> to be set to true.
     *
     * Modes:
     *
     * select - Left mouse button used for rubberband- & cell-selection.
     * connect - Allows connecting vertices by inserting new edges.
     * pan - Disables selection and switches to panning on the left button.
     *
     * Example:
     *
     * To add items to the toolbar:
     *
     * (code)
     * <mxDefaultToolbar as="toolbar">
     *   <add as="save" action="save" icon="images/save.gif"/>
     *   <br/><hr/>
     *   <add as="select" mode="select" icon="images/select.gif"/>
     *   <add as="connect" mode="connect" icon="images/connect.gif"/>
     * </mxDefaultToolbar>
     * (end)
     */
    codec.decode = function (dec, node, into) {
      if (into != null) {
        var editor = into.editor;
        node = node.firstChild;

        while (node != null) {
          if (node.nodeType == mxConstants.NODETYPE_ELEMENT) {
            if (!this.processInclude(dec, node, into)) {
              if (node.nodeName == 'separator') {
                into.addSeparator();
              } else if (node.nodeName == 'br') {
                into.toolbar.addBreak();
              } else if (node.nodeName == 'hr') {
                into.toolbar.addLine();
              } else if (node.nodeName == 'add') {
                var as = node.getAttribute('as');
                as = mxResources.get(as) || as;
                var icon = node.getAttribute('icon');
                var pressedIcon = node.getAttribute('pressedIcon');
                var action = node.getAttribute('action');
                var mode = node.getAttribute('mode');
                var template = node.getAttribute('template');
                var toggle = node.getAttribute('toggle') != '0';
                var text = mxUtils.getTextContent(node);
                var elt = null;

                if (action != null) {
                  elt = into.addItem(as, icon, action, pressedIcon);
                } else if (mode != null) {
                  var funct = mxDefaultToolbarCodec.allowEval ? mxUtils.eval(text) : null;
                  elt = into.addMode(as, icon, mode, pressedIcon, funct);
                } else if (template != null || (text != null && text.length > 0)) {
                  var cell = editor.templates[template];
                  var style = node.getAttribute('style');

                  if (cell != null && style != null) {
                    cell = editor.graph.cloneCell(cell);
                    cell.setStyle(style);
                  }

                  var insertFunction = null;

                  if (text != null && text.length > 0 && mxDefaultToolbarCodec.allowEval) {
                    insertFunction = mxUtils.eval(text);
                  }

                  elt = into.addPrototype(as, icon, cell, pressedIcon, insertFunction, toggle);
                } else {
                  var children = mxUtils.getChildNodes(node);

                  if (children.length > 0) {
                    if (icon == null) {
                      var combo = into.addActionCombo(as);

                      for (var i = 0; i < children.length; i++) {
                        var child = children[i];

                        if (child.nodeName == 'separator') {
                          into.addOption(combo, '---');
                        } else if (child.nodeName == 'add') {
                          var lab = child.getAttribute('as');
                          var act = child.getAttribute('action');
                          into.addActionOption(combo, lab, act);
                        }
                      }
                    } else {
                      var select = null;
                      var create = function () {
                        var template = editor.templates[select.value];

                        if (template != null) {
                          var clone = template.clone();
                          var style = select.options[select.selectedIndex].cellStyle;

                          if (style != null) {
                            clone.setStyle(style);
                          }

                          return clone;
                        } else {
                          mxLog.warn('Template ' + template + ' not found');
                        }

                        return null;
                      };

                      var img = into.addPrototype(as, icon, create, null, null, toggle);
                      select = into.addCombo();

                      // Selects the toolbar icon if a selection change
                      // is made in the corresponding combobox.
                      mxEvent.addListener(select, 'change', function () {
                        into.toolbar.selectMode(img, function (evt) {
                          var pt = mxUtils.convertPoint(
                            editor.graph.container,
                            mxEvent.getClientX(evt),
                            mxEvent.getClientY(evt)
                          );

                          return editor.addVertex(null, funct(), pt.x, pt.y);
                        });

                        into.toolbar.noReset = false;
                      });

                      // Adds the entries to the combobox
                      for (var i = 0; i < children.length; i++) {
                        var child = children[i];

                        if (child.nodeName == 'separator') {
                          into.addOption(select, '---');
                        } else if (child.nodeName == 'add') {
                          var lab = child.getAttribute('as');
                          var tmp = child.getAttribute('template');
                          var option = into.addOption(select, lab, tmp || template);
                          option.cellStyle = child.getAttribute('style');
                        }
                      }
                    }
                  }
                }

                // Assigns an ID to the created element to access it later.
                if (elt != null) {
                  var id = node.getAttribute('id');

                  if (id != null && id.length > 0) {
                    elt.setAttribute('id', id);
                  }
                }
              }
            }
          }

          node = node.nextSibling;
        }
      }

      return into;
    };

    // Returns the codec into the registry
    return codec;
  })()
);

/**
 * Variable: allowEval
 *
 * Static global switch that specifies if the use of eval is allowed for
 * evaluating text content. Default is true. Set this to false if stylesheets
 * may contain user input
 */
mxDefaultToolbarCodec.allowEval = true;

/**
 * io/mxDefaultPopupMenuCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxDefaultPopupMenuCodec
     *
     * Custom codec for configuring <mxDefaultPopupMenu>s. This class is created
     * and registered dynamically at load time and used implicitly via
     * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
     * data for existing popup menus, it does not encode or create menus. Note
     * that this codec only passes the configuration node to the popup menu,
     * which uses the config to dynamically create menus. See
     * <mxDefaultPopupMenu.createMenu>.
     */
    var codec = new mxObjectCodec(new mxDefaultPopupMenu());

    /**
     * Function: encode
     *
     * Returns null.
     */
    codec.encode = function (enc, obj) {
      return null;
    };

    /**
     * Function: decode
     *
     * Uses the given node as the config for <mxDefaultPopupMenu>.
     */
    codec.decode = function (dec, node, into) {
      var inc = node.getElementsByTagName('include')[0];

      if (inc != null) {
        this.processInclude(dec, inc, into);
      } else if (into != null) {
        into.config = node;
      }

      return into;
    };

    // Returns the codec into the registry
    return codec;
  })()
);

/**
 * io/mxEditorCodec.js
 */
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
mxCodecRegistry.register(
  (function () {
    /**
     * Class: mxEditorCodec
     *
     * Codec for <mxEditor>s. This class is created and registered
     * dynamically at load time and used implicitly via <mxCodec>
     * and the <mxCodecRegistry>.
     *
     * Transient Fields:
     *
     * - modified
     * - lastSnapshot
     * - ignoredChanges
     * - undoManager
     * - graphContainer
     * - toolbarContainer
     */
    var codec = new mxObjectCodec(new mxEditor(), [
      'modified',
      'lastSnapshot',
      'ignoredChanges',
      'undoManager',
      'graphContainer',
      'toolbarContainer',
    ]);

    /**
     * Function: beforeDecode
     *
     * Decodes the ui-part of the configuration node by reading
     * a sequence of the following child nodes and attributes
     * and passes the control to the default decoding mechanism:
     *
     * Child Nodes:
     *
     * stylesheet - Adds a CSS stylesheet to the document.
     * resource - Adds the basename of a resource bundle.
     * add - Creates or configures a known UI element.
     *
     * These elements may appear in any order given that the
     * graph UI element is added before the toolbar element
     * (see Known Keys).
     *
     * Attributes:
     *
     * as - Key for the UI element (see below).
     * element - ID for the element in the document.
     * style - CSS style to be used for the element or window.
     * x - X coordinate for the new window.
     * y - Y coordinate for the new window.
     * width - Width for the new window.
     * height - Optional height for the new window.
     * name - Name of the stylesheet (absolute/relative URL).
     * basename - Basename of the resource bundle (see <mxResources>).
     *
     * The x, y, width and height attributes are used to create a new
     * <mxWindow> if the element attribute is not specified in an add
     * node. The name and basename are only used in the stylesheet and
     * resource nodes, respectively.
     *
     * Known Keys:
     *
     * graph - Main graph element (see <mxEditor.setGraphContainer>).
     * title - Title element (see <mxEditor.setTitleContainer>).
     * toolbar - Toolbar element (see <mxEditor.setToolbarContainer>).
     * status - Status bar element (see <mxEditor.setStatusContainer>).
     *
     * Example:
     *
     * (code)
     * <ui>
     *   <stylesheet name="css/process.css"/>
     *   <resource basename="resources/app"/>
     *   <add as="graph" element="graph"
     *     style="left:70px;right:20px;top:20px;bottom:40px"/>
     *   <add as="status" element="status"/>
     *   <add as="toolbar" x="10" y="20" width="54"/>
     * </ui>
     * (end)
     */
    codec.afterDecode = function (dec, node, obj) {
      // Assigns the specified templates for edges
      var defaultEdge = node.getAttribute('defaultEdge');

      if (defaultEdge != null) {
        node.removeAttribute('defaultEdge');
        obj.defaultEdge = obj.templates[defaultEdge];
      }

      // Assigns the specified templates for groups
      var defaultGroup = node.getAttribute('defaultGroup');

      if (defaultGroup != null) {
        node.removeAttribute('defaultGroup');
        obj.defaultGroup = obj.templates[defaultGroup];
      }

      return obj;
    };

    /**
     * Function: decodeChild
     *
     * Overrides decode child to handle special child nodes.
     */
    codec.decodeChild = function (dec, child, obj) {
      if (child.nodeName == 'Array') {
        var role = child.getAttribute('as');

        if (role == 'templates') {
          this.decodeTemplates(dec, child, obj);
          return;
        }
      } else if (child.nodeName == 'ui') {
        this.decodeUi(dec, child, obj);
        return;
      }

      mxObjectCodec.prototype.decodeChild.apply(this, arguments);
    };

    /**
     * Function: decodeUi
     *
     * Decodes the ui elements from the given node.
     */
    codec.decodeUi = function (dec, node, editor) {
      var tmp = node.firstChild;
      while (tmp != null) {
        if (tmp.nodeName == 'add') {
          var as = tmp.getAttribute('as');
          var elt = tmp.getAttribute('element');
          var style = tmp.getAttribute('style');
          var element = null;

          if (elt != null) {
            element = document.getElementById(elt);

            if (element != null && style != null) {
              element.style.cssText += ';' + style;
            }
          } else {
            var x = parseInt(tmp.getAttribute('x'));
            var y = parseInt(tmp.getAttribute('y'));
            var width = tmp.getAttribute('width');
            var height = tmp.getAttribute('height');

            // Creates a new window around the element
            element = document.createElement('div');
            element.style.cssText = style;

            var wnd = new mxWindow(mxResources.get(as) || as, element, x, y, width, height, false, true);
            wnd.setVisible(true);
          }

          // TODO: Make more generic
          if (as == 'graph') {
            editor.setGraphContainer(element);
          } else if (as == 'toolbar') {
            editor.setToolbarContainer(element);
          } else if (as == 'title') {
            editor.setTitleContainer(element);
          } else if (as == 'status') {
            editor.setStatusContainer(element);
          } else if (as == 'map') {
            editor.setMapContainer(element);
          }
        } else if (tmp.nodeName == 'resource') {
          mxResources.add(tmp.getAttribute('basename'));
        } else if (tmp.nodeName == 'stylesheet') {
          mxClient.link('stylesheet', tmp.getAttribute('name'));
        }

        tmp = tmp.nextSibling;
      }
    };

    /**
     * Function: decodeTemplates
     *
     * Decodes the cells from the given node as templates.
     */
    codec.decodeTemplates = function (dec, node, editor) {
      if (editor.templates == null) {
        editor.templates = [];
      }

      var children = mxUtils.getChildNodes(node);
      for (var j = 0; j < children.length; j++) {
        var name = children[j].getAttribute('as');
        var child = children[j].firstChild;

        while (child != null && child.nodeType != 1) {
          child = child.nextSibling;
        }

        if (child != null) {
          // LATER: Only single cells means you need
          // to group multiple cells within another
          // cell. This should be changed to support
          // arrays of cells, or the wrapper must
          // be automatically handled in this class.
          editor.templates[name] = dec.decodeCell(child);
        }
      }
    };

    // Returns the codec into the registry
    return codec;
  })()
);
/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
/**
 * Constructs a new graph editor
 */
EditorUi = function (editor, container, lightbox) {
  mxEventSource.call(this);

  this.destroyFunctions = [];
  this.editor = editor || new Editor();
  this.container = container || document.body;

  var graph = this.editor.graph;
  graph.lightbox = lightbox;
  this.initialDefaultVertexStyle = mxUtils.clone(graph.defaultVertexStyle);
  this.initialDefaultEdgeStyle = mxUtils.clone(graph.defaultEdgeStyle);

  // Faster scrollwheel zoom is possible with CSS transforms
  if (graph.useCssTransforms) {
    this.lazyZoomDelay = 0;
  }

  // Pre-fetches submenu image or replaces with embedded image if supported
  if (mxClient.IS_SVG) {
    mxPopupMenu.prototype.submenuImage =
      'data:image/gif;base64,R0lGODlhCQAJAIAAAP///zMzMyH5BAEAAAAALAAAAAAJAAkAAAIPhI8WebHsHopSOVgb26AAADs=';
  } else {
    new Image().src = mxPopupMenu.prototype.submenuImage;
  }

  // Pre-fetches connect image
  if (!mxClient.IS_SVG && mxConnectionHandler.prototype.connectImage != null) {
    new Image().src = mxConnectionHandler.prototype.connectImage.src;
  }

  this.footerHeight = 0;

  // Disables graph and forced panning in chromeless mode
  if (this.editor.chromeless && !this.editor.editable) {
    this.footerHeight = 0;
    graph.isEnabled = function () {
      return false;
    };
    graph.panningHandler.isForcePanningEvent = function (me) {
      return !mxEvent.isPopupTrigger(me.getEvent());
    };
  }

  // Creates the user interface
  this.actions = new Actions(this);
  this.menus = this.createMenus();

  if (!graph.standalone) {
    this.createDivs();
    this.createUi();
    this.refresh();

    // Disables HTML and text selection
    var textEditing = mxUtils.bind(this, function (evt) {
      if (evt == null) {
        evt = window.event;
      }

      return graph.isEditing() || (evt != null && this.isSelectionAllowed(evt));
    });

    // Disables text selection while not editing and no dialog visible
    if (this.container == document.body) {
      this.menubarContainer.onselectstart = textEditing;
      this.menubarContainer.onmousedown = textEditing;
      this.toolbarContainer.onselectstart = textEditing;
      this.toolbarContainer.onmousedown = textEditing;
      this.diagramContainer.onselectstart = textEditing;
      this.diagramContainer.onmousedown = textEditing;
      this.sidebarContainer.onselectstart = textEditing;
      this.sidebarContainer.onmousedown = textEditing;
      this.formatContainer.onselectstart = textEditing;
      this.formatContainer.onmousedown = textEditing;
      // this.footerContainer.onselectstart = textEditing;
      // this.footerContainer.onmousedown = textEditing;

      if (this.tabContainer != null) {
        // Mouse down is needed for drag and drop
        this.tabContainer.onselectstart = textEditing;
      }
    }

    // And uses built-in context menu while editing
    if (!this.editor.chromeless || this.editor.editable) {
      // Allows context menu for links in hints
      var linkHandler = function (evt) {
        if (evt != null) {
          var source = mxEvent.getSource(evt);

          if (source.nodeName == 'A') {
            while (source != null) {
              if (source.className == 'geHint') {
                return true;
              }

              source = source.parentNode;
            }
          }
        }

        return textEditing(evt);
      };

      if (mxClient.IS_IE && (typeof document.documentMode === 'undefined' || document.documentMode < 9)) {
        mxEvent.addListener(this.diagramContainer, 'contextmenu', linkHandler);
      } else {
        // Allows browser context menu outside of diagram and sidebar
        this.diagramContainer.oncontextmenu = linkHandler;
      }
    } else {
      graph.panningHandler.usePopupTrigger = false;
    }

    // Contains the main graph instance inside the given panel
    graph.init(this.diagramContainer);

    // Improves line wrapping for in-place editor
    if (mxClient.IS_SVG && graph.view.getDrawPane() != null) {
      var root = graph.view.getDrawPane().ownerSVGElement;

      if (root != null) {
        root.style.position = 'absolute';
      }
    }

    // Creates hover icons
    this.hoverIcons = this.createHoverIcons();

    // Hides hover icons when cells are moved
    if (graph.graphHandler != null) {
      var graphHandlerStart = graph.graphHandler.start;

      graph.graphHandler.start = function () {
        if (ui.hoverIcons != null) {
          ui.hoverIcons.reset();
        }

        graphHandlerStart.apply(this, arguments);
      };
    }

    // Adds tooltip when mouse is over scrollbars to show space-drag panning option
    mxEvent.addListener(
      this.diagramContainer,
      'mousemove',
      mxUtils.bind(this, function (evt) {
        var off = mxUtils.getOffset(this.diagramContainer);

        if (
          mxEvent.getClientX(evt) - off.x - this.diagramContainer.clientWidth > 0 ||
          mxEvent.getClientY(evt) - off.y - this.diagramContainer.clientHeight > 0
        ) {
          this.diagramContainer.setAttribute('title', mxResources.get('panTooltip'));
        } else {
          this.diagramContainer.removeAttribute('title');
        }
      })
    );

    // Escape key hides dialogs, adds space+drag panning
    var spaceKeyPressed = false;

    // Overrides hovericons to disable while space key is pressed
    var hoverIconsIsResetEvent = this.hoverIcons.isResetEvent;

    this.hoverIcons.isResetEvent = function (evt, allowShift) {
      return spaceKeyPressed || hoverIconsIsResetEvent.apply(this, arguments);
    };

    this.keydownHandler = mxUtils.bind(this, function (evt) {
      if (evt.which == 32 /* Space */ && !graph.isEditing()) {
        spaceKeyPressed = true;
        this.hoverIcons.reset();
        graph.container.style.cursor = 'move';

        // Disables scroll after space keystroke with scrollbars
        if (!graph.isEditing() && mxEvent.getSource(evt) == graph.container) {
          mxEvent.consume(evt);
        }
      } else if (!mxEvent.isConsumed(evt) && evt.keyCode == 27 /* Escape */) {
        this.hideDialog(null, true);
      }
    });

    mxEvent.addListener(document, 'keydown', this.keydownHandler);

    this.keyupHandler = mxUtils.bind(this, function (evt) {
      graph.container.style.cursor = '';
      spaceKeyPressed = false;
    });

    mxEvent.addListener(document, 'keyup', this.keyupHandler);

    // Forces panning for middle and right mouse buttons
    var panningHandlerIsForcePanningEvent = graph.panningHandler.isForcePanningEvent;
    graph.panningHandler.isForcePanningEvent = function (me) {
      // Ctrl+left button is reported as right button in FF on Mac
      return (
        panningHandlerIsForcePanningEvent.apply(this, arguments) ||
        spaceKeyPressed ||
        (mxEvent.isMouseEvent(me.getEvent()) &&
          (this.usePopupTrigger || !mxEvent.isPopupTrigger(me.getEvent())) &&
          ((!mxEvent.isControlDown(me.getEvent()) && mxEvent.isRightMouseButton(me.getEvent())) ||
            mxEvent.isMiddleMouseButton(me.getEvent())))
      );
    };

    // Ctrl/Cmd+Enter applies editing value except in Safari where Ctrl+Enter creates
    // a new line (while Enter creates a new paragraph and Shift+Enter stops)
    var cellEditorIsStopEditingEvent = graph.cellEditor.isStopEditingEvent;
    graph.cellEditor.isStopEditingEvent = function (evt) {
      return (
        cellEditorIsStopEditingEvent.apply(this, arguments) ||
        (evt.keyCode == 13 &&
          ((!mxClient.IS_SF && mxEvent.isControlDown(evt)) ||
            (mxClient.IS_MAC && mxEvent.isMetaDown(evt)) ||
            (mxClient.IS_SF && mxEvent.isShiftDown(evt))))
      );
    };

    // Adds space+wheel for zoom
    var graphIsZoomWheelEvent = graph.isZoomWheelEvent;

    graph.isZoomWheelEvent = function () {
      return spaceKeyPressed || graphIsZoomWheelEvent.apply(this, arguments);
    };

    // Switches toolbar for text editing
    var textMode = false;
    var fontMenu = null;
    var sizeMenu = null;
    var nodes = null;

    var updateToolbar = mxUtils.bind(this, function () {
      if (this.toolbar != null && textMode != graph.cellEditor.isContentEditing()) {
        var node = this.toolbar.container.firstChild;
        var newNodes = [];

        while (node != null) {
          var tmp = node.nextSibling;

          if (mxUtils.indexOf(this.toolbar.staticElements, node) < 0) {
            node.parentNode.removeChild(node);
            newNodes.push(node);
          }

          node = tmp;
        }

        // Saves references to special items
        var tmp1 = this.toolbar.fontMenu;
        var tmp2 = this.toolbar.sizeMenu;

        if (nodes == null) {
          this.toolbar.createTextToolbar();
        } else {
          for (var i = 0; i < nodes.length; i++) {
            this.toolbar.container.appendChild(nodes[i]);
          }

          // Restores references to special items
          this.toolbar.fontMenu = fontMenu;
          this.toolbar.sizeMenu = sizeMenu;
        }

        textMode = graph.cellEditor.isContentEditing();
        fontMenu = tmp1;
        sizeMenu = tmp2;
        nodes = newNodes;
      }
    });

    var ui = this;

    // Overrides cell editor to update toolbar
    var cellEditorStartEditing = graph.cellEditor.startEditing;
    graph.cellEditor.startEditing = function () {
      cellEditorStartEditing.apply(this, arguments);
      updateToolbar();

      if (graph.cellEditor.isContentEditing()) {
        var updating = false;

        var updateCssHandler = function () {
          if (!updating) {
            updating = true;

            window.setTimeout(function () {
              var node = graph.getSelectedEditingElement();

              if (node != null) {
                var css = mxUtils.getCurrentStyle(node);

                if (css != null && ui.toolbar != null) {
                  ui.toolbar.setFontName(Graph.stripQuotes(css.fontFamily));
                  ui.toolbar.setFontSize(parseInt(css.fontSize));
                }
              }

              updating = false;
            }, 0);
          }
        };

        mxEvent.addListener(graph.cellEditor.textarea, 'input', updateCssHandler);
        mxEvent.addListener(graph.cellEditor.textarea, 'touchend', updateCssHandler);
        mxEvent.addListener(graph.cellEditor.textarea, 'mouseup', updateCssHandler);
        mxEvent.addListener(graph.cellEditor.textarea, 'keyup', updateCssHandler);
        updateCssHandler();
      }
    };

    // Updates toolbar and handles possible errors
    var cellEditorStopEditing = graph.cellEditor.stopEditing;
    graph.cellEditor.stopEditing = function (cell, trigger) {
      try {
        cellEditorStopEditing.apply(this, arguments);
        updateToolbar();
      } catch (e) {
        ui.handleError(e);
      }
    };

    // Enables scrollbars and sets cursor style for the container
    graph.container.setAttribute('tabindex', '0');
    graph.container.style.cursor = 'default';

    // Workaround for page scroll if embedded via iframe
    if (window.self === window.top && graph.container.parentNode != null) {
      try {
        graph.container.focus();
      } catch (e) {
        // ignores error in old versions of IE
      }
    }

    // Keeps graph container focused on mouse down
    var graphFireMouseEvent = graph.fireMouseEvent;
    graph.fireMouseEvent = function (evtName, me, sender) {
      if (evtName == mxEvent.MOUSE_DOWN) {
        this.container.focus();
      }

      graphFireMouseEvent.apply(this, arguments);
    };

    // Configures automatic expand on mouseover
    graph.popupMenuHandler.autoExpand = true;

    // Installs context menu
    if (this.menus != null) {
      graph.popupMenuHandler.factoryMethod = mxUtils.bind(this, function (menu, cell, evt) {
        this.menus.createPopupMenu(menu, cell, evt);
      });
    }

    // Hides context menu
    mxEvent.addGestureListeners(
      document,
      mxUtils.bind(this, function (evt) {
        graph.popupMenuHandler.hideMenu();
      })
    );

    // Create handler for key events
    this.keyHandler = this.createKeyHandler(editor);

    // Getter for key handler
    this.getKeyHandler = function () {
      return keyHandler;
    };

    // Stores the current style and assigns it to new cells
    var styles = [
      'rounded',
      'shadow',
      'glass',
      'dashed',
      'dashPattern',
      'labelBackgroundColor',
      'comic',
      'sketch',
      'fillWeight',
      'hachureGap',
      'hachureAngle',
      'jiggle',
      'disableMultiStroke',
      'disableMultiStrokeFill',
      'fillStyle',
      'curveFitting',
      'simplification',
      'sketchStyle',
    ];
    var connectStyles = [
      'shape',
      'edgeStyle',
      'curved',
      'rounded',
      'elbow',
      'jumpStyle',
      'jumpSize',
      'comic',
      'sketch',
      'fillWeight',
      'hachureGap',
      'hachureAngle',
      'jiggle',
      'disableMultiStroke',
      'disableMultiStrokeFill',
      'fillStyle',
      'curveFitting',
      'simplification',
      'sketchStyle',
    ];

    // Note: Everything that is not in styles is ignored (styles is augmented below)
    this.setDefaultStyle = function (cell) {
      try {
        var state = graph.view.getState(cell);

        if (state != null) {
          // Ignores default styles
          var clone = cell.clone();
          clone.style = '';
          var defaultStyle = graph.getCellStyle(clone);
          var values = [];
          var keys = [];

          for (var key in state.style) {
            if (defaultStyle[key] != state.style[key]) {
              values.push(state.style[key]);
              keys.push(key);
            }
          }

          // Handles special case for value "none"
          var cellStyle = graph.getModel().getStyle(state.cell);
          var tokens = cellStyle != null ? cellStyle.split(';') : [];

          for (var i = 0; i < tokens.length; i++) {
            var tmp = tokens[i];
            var pos = tmp.indexOf('=');

            if (pos >= 0) {
              var key = tmp.substring(0, pos);
              var value = tmp.substring(pos + 1);

              if (defaultStyle[key] != null && value == 'none') {
                values.push(value);
                keys.push(key);
              }
            }
          }

          // Resets current style
          if (graph.getModel().isEdge(state.cell)) {
            graph.currentEdgeStyle = {};
          } else {
            graph.currentVertexStyle = {};
          }

          this.fireEvent(new mxEventObject('styleChanged', 'keys', keys, 'values', values, 'cells', [state.cell]));
        }
      } catch (e) {
        this.handleError(e);
      }
    };

    this.clearDefaultStyle = function () {
      graph.currentEdgeStyle = mxUtils.clone(graph.defaultEdgeStyle);
      graph.currentVertexStyle = mxUtils.clone(graph.defaultVertexStyle);

      // Updates UI
      this.fireEvent(new mxEventObject('styleChanged', 'keys', [], 'values', [], 'cells', []));
    };

    // Keys that should be ignored if the cell has a value (known: new default for all cells is html=1 so
    // for the html key this effecticely only works for edges inserted via the connection handler)
    var valueStyles = ['fontFamily', 'fontSource', 'fontSize', 'fontColor'];

    // Keys that always update the current edge style regardless of selection
    var alwaysEdgeStyles = ['edgeStyle', 'startArrow', 'startFill', 'startSize', 'endArrow', 'endFill', 'endSize'];

    // Keys that are ignored together (if one appears all are ignored)
    var keyGroups = [
      [
        'startArrow',
        'startFill',
        'startSize',
        'sourcePerimeterSpacing',
        'endArrow',
        'endFill',
        'endSize',
        'targetPerimeterSpacing',
      ],
      ['strokeColor', 'strokeWidth'],
      ['fillColor', 'gradientColor'],
      valueStyles,
      ['opacity'],
      ['align'],
      ['html'],
    ];

    // Adds all keys used above to the styles array
    for (var i = 0; i < keyGroups.length; i++) {
      for (var j = 0; j < keyGroups[i].length; j++) {
        styles.push(keyGroups[i][j]);
      }
    }

    for (var i = 0; i < connectStyles.length; i++) {
      if (mxUtils.indexOf(styles, connectStyles[i]) < 0) {
        styles.push(connectStyles[i]);
      }
    }

    // Implements a global current style for edges and vertices that is applied to new cells
    var insertHandler = function (cells, asText, model) {
      model = model != null ? model : graph.getModel();

      model.beginUpdate();
      try {
        for (var i = 0; i < cells.length; i++) {
          var cell = cells[i];

          var appliedStyles;

          if (asText) {
            // Applies only basic text styles
            appliedStyles = ['fontSize', 'fontFamily', 'fontColor'];
          } else {
            // Removes styles defined in the cell style from the styles to be applied
            var cellStyle = model.getStyle(cell);
            var tokens = cellStyle != null ? cellStyle.split(';') : [];
            appliedStyles = styles.slice();

            for (var j = 0; j < tokens.length; j++) {
              var tmp = tokens[j];
              var pos = tmp.indexOf('=');

              if (pos >= 0) {
                var key = tmp.substring(0, pos);
                var index = mxUtils.indexOf(appliedStyles, key);

                if (index >= 0) {
                  appliedStyles.splice(index, 1);
                }

                // Handles special cases where one defined style ignores other styles
                for (var k = 0; k < keyGroups.length; k++) {
                  var group = keyGroups[k];

                  if (mxUtils.indexOf(group, key) >= 0) {
                    for (var l = 0; l < group.length; l++) {
                      var index2 = mxUtils.indexOf(appliedStyles, group[l]);

                      if (index2 >= 0) {
                        appliedStyles.splice(index2, 1);
                      }
                    }
                  }
                }
              }
            }
          }

          // Applies the current style to the cell
          var edge = model.isEdge(cell);
          var current = edge ? graph.currentEdgeStyle : graph.currentVertexStyle;
          var newStyle = model.getStyle(cell);

          for (var j = 0; j < appliedStyles.length; j++) {
            var key = appliedStyles[j];
            var styleValue = current[key];

            if (styleValue != null && (key != 'shape' || edge)) {
              // Special case: Connect styles are not applied here but in the connection handler
              if (!edge || mxUtils.indexOf(connectStyles, key) < 0) {
                newStyle = mxUtils.setStyle(newStyle, key, styleValue);
              }
            }
          }

          model.setStyle(cell, newStyle);
        }
      } finally {
        model.endUpdate();
      }
    };

    graph.addListener('cellsInserted', function (sender, evt) {
      insertHandler(evt.getProperty('cells'));
    });

    graph.addListener('textInserted', function (sender, evt) {
      insertHandler(evt.getProperty('cells'), true);
    });

    this.insertHandler = insertHandler;

    graph.connectionHandler.addListener(mxEvent.CONNECT, function (sender, evt) {
      var cells = [evt.getProperty('cell')];

      if (evt.getProperty('terminalInserted')) {
        cells.push(evt.getProperty('terminal'));
      }

      insertHandler(cells);
    });

    this.addListener(
      'styleChanged',
      mxUtils.bind(this, function (sender, evt) {
        // Checks if edges and/or vertices were modified
        var cells = evt.getProperty('cells');
        var vertex = false;
        var edge = false;

        if (cells.length > 0) {
          for (var i = 0; i < cells.length; i++) {
            vertex = graph.getModel().isVertex(cells[i]) || vertex;
            edge = graph.getModel().isEdge(cells[i]) || edge;

            if (edge && vertex) {
              break;
            }
          }
        } else {
          vertex = true;
          edge = true;
        }

        var keys = evt.getProperty('keys');
        var values = evt.getProperty('values');

        for (var i = 0; i < keys.length; i++) {
          var common = mxUtils.indexOf(valueStyles, keys[i]) >= 0;

          // Ignores transparent stroke colors
          if (keys[i] != 'strokeColor' || (values[i] != null && values[i] != 'none')) {
            // Special case: Edge style and shape
            if (mxUtils.indexOf(connectStyles, keys[i]) >= 0) {
              if (edge || mxUtils.indexOf(alwaysEdgeStyles, keys[i]) >= 0) {
                if (values[i] == null) {
                  delete graph.currentEdgeStyle[keys[i]];
                } else {
                  graph.currentEdgeStyle[keys[i]] = values[i];
                }
              }
              // Uses style for vertex if defined in styles
              else if (vertex && mxUtils.indexOf(styles, keys[i]) >= 0) {
                if (values[i] == null) {
                  delete graph.currentVertexStyle[keys[i]];
                } else {
                  graph.currentVertexStyle[keys[i]] = values[i];
                }
              }
            } else if (mxUtils.indexOf(styles, keys[i]) >= 0) {
              if (vertex || common) {
                if (values[i] == null) {
                  delete graph.currentVertexStyle[keys[i]];
                } else {
                  graph.currentVertexStyle[keys[i]] = values[i];
                }
              }

              if (edge || common || mxUtils.indexOf(alwaysEdgeStyles, keys[i]) >= 0) {
                if (values[i] == null) {
                  delete graph.currentEdgeStyle[keys[i]];
                } else {
                  graph.currentEdgeStyle[keys[i]] = values[i];
                }
              }
            }
          }
        }

        if (this.toolbar != null) {
          this.toolbar.setFontName(graph.currentVertexStyle['fontFamily'] || Menus.prototype.defaultFont);
          this.toolbar.setFontSize(graph.currentVertexStyle['fontSize'] || Menus.prototype.defaultFontSize);

          if (this.toolbar.edgeStyleMenu != null) {
            // Updates toolbar icon for edge style
            var edgeStyleDiv = this.toolbar.edgeStyleMenu.getElementsByTagName('div')[0];

            if (
              graph.currentEdgeStyle['edgeStyle'] == 'orthogonalEdgeStyle' &&
              graph.currentEdgeStyle['curved'] == '1'
            ) {
              edgeStyleDiv.className = 'geSprite geSprite-curved';
            } else if (
              graph.currentEdgeStyle['edgeStyle'] == 'straight' ||
              graph.currentEdgeStyle['edgeStyle'] == 'none' ||
              graph.currentEdgeStyle['edgeStyle'] == null
            ) {
              edgeStyleDiv.className = 'geSprite geSprite-straight';
            } else if (graph.currentEdgeStyle['edgeStyle'] == 'entityRelationEdgeStyle') {
              edgeStyleDiv.className = 'geSprite geSprite-entity';
            } else if (graph.currentEdgeStyle['edgeStyle'] == 'elbowEdgeStyle') {
              edgeStyleDiv.className =
                'geSprite geSprite-' +
                (graph.currentEdgeStyle['elbow'] == 'vertical' ? 'verticalelbow' : 'horizontalelbow');
            } else if (graph.currentEdgeStyle['edgeStyle'] == 'isometricEdgeStyle') {
              edgeStyleDiv.className =
                'geSprite geSprite-' +
                (graph.currentEdgeStyle['elbow'] == 'vertical' ? 'verticalisometric' : 'horizontalisometric');
            } else {
              edgeStyleDiv.className = 'geSprite geSprite-orthogonal';
            }
          }

          if (this.toolbar.edgeShapeMenu != null) {
            // Updates icon for edge shape
            var edgeShapeDiv = this.toolbar.edgeShapeMenu.getElementsByTagName('div')[0];

            if (graph.currentEdgeStyle['shape'] == 'link') {
              edgeShapeDiv.className = 'geSprite geSprite-linkedge';
            } else if (graph.currentEdgeStyle['shape'] == 'flexArrow') {
              edgeShapeDiv.className = 'geSprite geSprite-arrow';
            } else if (graph.currentEdgeStyle['shape'] == 'arrow') {
              edgeShapeDiv.className = 'geSprite geSprite-simplearrow';
            } else {
              edgeShapeDiv.className = 'geSprite geSprite-connection';
            }
          }

          // Updates icon for optinal line start shape
          if (this.toolbar.lineStartMenu != null) {
            var lineStartDiv = this.toolbar.lineStartMenu.getElementsByTagName('div')[0];

            lineStartDiv.className = this.getCssClassForMarker(
              'start',
              graph.currentEdgeStyle['shape'],
              graph.currentEdgeStyle[mxConstants.STYLE_STARTARROW],
              mxUtils.getValue(graph.currentEdgeStyle, 'startFill', '1')
            );
          }

          // Updates icon for optinal line end shape
          if (this.toolbar.lineEndMenu != null) {
            var lineEndDiv = this.toolbar.lineEndMenu.getElementsByTagName('div')[0];

            lineEndDiv.className = this.getCssClassForMarker(
              'end',
              graph.currentEdgeStyle['shape'],
              graph.currentEdgeStyle[mxConstants.STYLE_ENDARROW],
              mxUtils.getValue(graph.currentEdgeStyle, 'endFill', '1')
            );
          }
        }
      })
    );

    // Update font size and font family labels
    if (this.toolbar != null) {
      var update = mxUtils.bind(this, function () {
        var ff = graph.currentVertexStyle['fontFamily'] || 'Helvetica';
        var fs = String(graph.currentVertexStyle['fontSize'] || '12');
        var state = graph.getView().getState(graph.getSelectionCell());

        if (state != null) {
          ff = state.style[mxConstants.STYLE_FONTFAMILY] || ff;
          fs = state.style[mxConstants.STYLE_FONTSIZE] || fs;

          if (ff.length > 10) {
            ff = ff.substring(0, 8) + '...';
          }
        }

        this.toolbar.setFontName(ff);
        this.toolbar.setFontSize(fs);
      });

      graph.getSelectionModel().addListener(mxEvent.CHANGE, update);
      graph.getModel().addListener(mxEvent.CHANGE, update);
    }

    // Makes sure the current layer is visible when cells are added
    graph.addListener(mxEvent.CELLS_ADDED, function (sender, evt) {
      var cells = evt.getProperty('cells');
      var parent = evt.getProperty('parent');

      if (graph.getModel().isLayer(parent) && !graph.isCellVisible(parent) && cells != null && cells.length > 0) {
        graph.getModel().setVisible(parent, true);
      }
    });

    // Global handler to hide the current menu
    this.gestureHandler = mxUtils.bind(this, function (evt) {
      if (this.currentMenu != null && mxEvent.getSource(evt) != this.currentMenu.div) {
        this.hideCurrentMenu();
      }
    });

    mxEvent.addGestureListeners(document, this.gestureHandler);

    // Updates the editor UI after the window has been resized or the orientation changes
    // Timeout is workaround for old IE versions which have a delay for DOM client sizes.
    // Should not use delay > 0 to avoid handle multiple repaints during window resize
    this.resizeHandler = mxUtils.bind(this, function () {
      window.setTimeout(
        mxUtils.bind(this, function () {
          if (this.editor.graph != null) {
            this.refresh();
          }
        }),
        0
      );
    });

    mxEvent.addListener(window, 'resize', this.resizeHandler);

    this.orientationChangeHandler = mxUtils.bind(this, function () {
      this.refresh();
    });

    mxEvent.addListener(window, 'orientationchange', this.orientationChangeHandler);

    // Workaround for bug on iOS see
    // http://stackoverflow.com/questions/19012135/ios-7-ipad-safari-landscape-innerheight-outerheight-layout-issue
    if (mxClient.IS_IOS && !window.navigator.standalone) {
      this.scrollHandler = mxUtils.bind(this, function () {
        window.scrollTo(0, 0);
      });

      mxEvent.addListener(window, 'scroll', this.scrollHandler);
    }

    /**
     * Sets the initial scrollbar locations after a file was loaded.
     */
    this.editor.addListener(
      'resetGraphView',
      mxUtils.bind(this, function () {
        this.resetScrollbars();
      })
    );

    /**
     * Repaints the grid.
     */
    this.addListener(
      'gridEnabledChanged',
      mxUtils.bind(this, function () {
        graph.view.validateBackground();
      })
    );

    this.addListener(
      'backgroundColorChanged',
      mxUtils.bind(this, function () {
        graph.view.validateBackground();
      })
    );

    /**
     * Repaints the grid.
     */
    graph.addListener(
      'gridSizeChanged',
      mxUtils.bind(this, function () {
        if (graph.isGridEnabled()) {
          graph.view.validateBackground();
        }
      })
    );

    // Resets UI, updates action and menu states
    this.editor.resetGraph();
  }

  this.init();

  if (!graph.standalone) {
    this.open();
  }
};

// Extends mxEventSource
mxUtils.extend(EditorUi, mxEventSource);

/**
 * Global config that specifies if the compact UI elements should be used.
 */
EditorUi.compactUi = true;

/**
 * Specifies the size of the split bar.
 */
EditorUi.prototype.splitSize = mxClient.IS_TOUCH || mxClient.IS_POINTER ? 12 : 8;

/**
 * Specifies the height of the menubar. Default is 30.
 */
EditorUi.prototype.menubarHeight = 30;

/**
 * Specifies the width of the format panel should be enabled. Default is true.
 */
EditorUi.prototype.formatEnabled = true;

/**
 * Specifies the width of the format panel. Default is 240.
 */
EditorUi.prototype.formatWidth = 240;

/**
 * Specifies the height of the toolbar. Default is 38.
 */
EditorUi.prototype.toolbarHeight = 38;

/**
 * Specifies the height of the footer. Default is 28.
 */
EditorUi.prototype.footerHeight = 28;

/**
 * Specifies the height of the optional sidebarFooterContainer. Default is 34.
 */
EditorUi.prototype.sidebarFooterHeight = 34;

/**
 * Specifies the position of the horizontal split bar. Default is 240 or 118 for
 * screen widths <= 640px.
 */
EditorUi.prototype.hsplitPosition = screen.width <= 640 ? 118 : urlParams['sidebar-entries'] != 'large' ? 212 : 240;

/**
 * Specifies if animations are allowed in <executeLayout>. Default is true.
 */
EditorUi.prototype.allowAnimation = true;

/**
 * Default is 2.
 */
EditorUi.prototype.lightboxMaxFitScale = 2;

/**
 * Default is 4.
 */
EditorUi.prototype.lightboxVerticalDivider = 4;

/**
 * Specifies if single click on horizontal split should collapse sidebar. Default is false.
 */
EditorUi.prototype.hsplitClickEnabled = false;

/**
 * Installs the listeners to update the action states.
 */
EditorUi.prototype.init = function () {
  var graph = this.editor.graph;

  if (!graph.standalone) {
    if (urlParams['shape-picker'] != '0') {
      this.installShapePicker();
    }

    // Hides tooltips and connection points when scrolling
    mxEvent.addListener(
      graph.container,
      'scroll',
      mxUtils.bind(this, function () {
        graph.tooltipHandler.hide();

        if (graph.connectionHandler != null && graph.connectionHandler.constraintHandler != null) {
          graph.connectionHandler.constraintHandler.reset();
        }
      })
    );

    // Hides tooltip on escape
    graph.addListener(
      mxEvent.ESCAPE,
      mxUtils.bind(this, function () {
        graph.tooltipHandler.hide();
        var rb = graph.getRubberband();

        if (rb != null) {
          rb.cancel();
        }
      })
    );

    mxEvent.addListener(
      graph.container,
      'keydown',
      mxUtils.bind(this, function (evt) {
        this.onKeyDown(evt);
      })
    );

    mxEvent.addListener(
      graph.container,
      'keypress',
      mxUtils.bind(this, function (evt) {
        this.onKeyPress(evt);
      })
    );

    // Updates action states
    this.addUndoListener();
    this.addBeforeUnloadListener();

    graph.getSelectionModel().addListener(
      mxEvent.CHANGE,
      mxUtils.bind(this, function () {
        this.updateActionStates();
      })
    );

    graph.getModel().addListener(
      mxEvent.CHANGE,
      mxUtils.bind(this, function () {
        this.updateActionStates();
      })
    );

    // Changes action states after change of default parent
    var graphSetDefaultParent = graph.setDefaultParent;
    var ui = this;

    this.editor.graph.setDefaultParent = function () {
      graphSetDefaultParent.apply(this, arguments);
      ui.updateActionStates();
    };

    // Hack to make editLink available in vertex handler
    graph.editLink = ui.actions.get('editLink').funct;

    this.updateActionStates();
    this.initClipboard();
    this.initCanvas();

    if (this.format != null) {
      this.format.init();
    }
  }
};

/**
 * Returns true if the given event should start editing. This implementation returns true.
 */
EditorUi.prototype.installShapePicker = function () {
  var graph = this.editor.graph;
  var ui = this;

  // Uses this event to process mouseDown to check the selection state before it is changed
  graph.addListener(
    mxEvent.FIRE_MOUSE_EVENT,
    mxUtils.bind(this, function (sender, evt) {
      if (evt.getProperty('eventName') == 'mouseDown') {
        ui.hideShapePicker();
      }
    })
  );

  graph.addListener(
    mxEvent.ESCAPE,
    mxUtils.bind(this, function () {
      ui.hideShapePicker(true);
    })
  );

  graph.getSelectionModel().addListener(
    mxEvent.CHANGE,
    mxUtils.bind(this, function () {
      ui.hideShapePicker(true);
    })
  );

  graph.getModel().addListener(
    mxEvent.CHANGE,
    mxUtils.bind(this, function () {
      ui.hideShapePicker(true);
    })
  );

  // Counts as popup menu
  var popupMenuHandlerIsMenuShowing = graph.popupMenuHandler.isMenuShowing;

  graph.popupMenuHandler.isMenuShowing = function () {
    return popupMenuHandlerIsMenuShowing.apply(this, arguments) || ui.shapePicker != null;
  };

  // Adds dbl click dialog for inserting shapes
  var graphDblClick = graph.dblClick;

  graph.dblClick = function (evt, cell) {
    if (this.isEnabled()) {
      if (cell == null && ui.sidebar != null && !mxEvent.isShiftDown(evt)) {
        mxEvent.consume(evt);
        var pt = mxUtils.convertPoint(this.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));

        // Asynchronous to avoid direct insert after double tap
        window.setTimeout(
          mxUtils.bind(this, function () {
            ui.showShapePicker(pt.x, pt.y);
          }),
          30
        );
      } else {
        graphDblClick.apply(this, arguments);
      }
    }
  };

  if (this.hoverIcons != null) {
    var hoverIconsDrag = this.hoverIcons.drag;

    this.hoverIcons.drag = function () {
      ui.hideShapePicker();
      hoverIconsDrag.apply(this, arguments);
    };

    var hoverIconsExecute = this.hoverIcons.execute;

    this.hoverIcons.execute = function (state, dir, me) {
      var evt = me.getEvent();

      if (!this.graph.isCloneEvent(evt) && !mxEvent.isShiftDown(evt)) {
        this.graph.connectVertex(
          state.cell,
          dir,
          this.graph.defaultEdgeLength,
          evt,
          null,
          null,
          mxUtils.bind(this, function (x, y, execute) {
            var temp = graph.getCompositeParent(state.cell);
            var geo = graph.getCellGeometry(temp);
            me.consume();

            while (temp != null && graph.model.isVertex(temp) && geo != null && geo.relative) {
              cell = temp;
              temp = graph.model.getParent(cell);
              geo = graph.getCellGeometry(temp);
            }

            // Asynchronous to avoid direct insert after double tap
            window.setTimeout(
              mxUtils.bind(this, function () {
                ui.showShapePicker(
                  me.getGraphX(),
                  me.getGraphY(),
                  temp,
                  mxUtils.bind(this, function (cell) {
                    execute(cell);
                  }),
                  dir
                );
              }),
              30
            );
          }),
          mxUtils.bind(this, function (result) {
            this.graph.selectCellsForConnectVertex(result, evt, this);
          })
        );
      } else {
        hoverIconsExecute.apply(this, arguments);
      }
    };
  }
};

/**
 * Creates a temporary graph instance for rendering off-screen content.
 */
EditorUi.prototype.showShapePicker = function (x, y, source, callback, direction) {
  var cells = this.getCellsForShapePicker(source);

  if (cells != null && cells.length > 0) {
    var ui = this;
    var graph = this.editor.graph;
    var div = document.createElement('div');
    var sourceState = graph.view.getState(source);
    var style =
      source != null && (sourceState == null || !graph.isTransparentState(sourceState))
        ? graph.copyStyle(source)
        : null;

    // Do not place entry under pointer for touch devices
    div.className = 'geToolbarContainer geSidebarContainer geSidebar';
    div.style.cssText =
      'position:absolute;left:' +
      x +
      'px;top:' +
      y +
      'px;width:140px;border-radius:10px;padding:4px;text-align:center;' +
      'box-shadow:0px 0px 3px 1px #d1d1d1;padding: 6px 0 8px 0;';
    mxUtils.setPrefixedStyle(div.style, 'transform', 'translate(-22px,-22px)');

    if (graph.background != null && graph.background != mxConstants.NONE) {
      div.style.backgroundColor = graph.background;
    }

    graph.container.appendChild(div);

    var addCell = mxUtils.bind(this, function (cell) {
      // Wrapper needed to catch events
      var node = document.createElement('a');
      node.className = 'geItem';
      node.style.cssText =
        'position:relative;display:inline-block;position:relative;' +
        'width:30px;height:30px;cursor:pointer;overflow:hidden;padding:3px 0 0 3px;';
      div.appendChild(node);

      if (style != null) {
        this.sidebar.graph.pasteStyle(style, [cell]);
      } else {
        ui.insertHandler([cell], cell.value != '', this.sidebar.graph.model);
      }

      this.sidebar.createThumb([cell], 25, 25, node, null, true, false, cell.geometry.width, cell.geometry.height);

      mxEvent.addListener(node, 'click', function () {
        var clone = graph.cloneCell(cell);

        if (callback != null) {
          callback(clone);
        } else {
          clone.geometry.x = graph.snap(
            Math.round(x / graph.view.scale) - graph.view.translate.x - cell.geometry.width / 2
          );
          clone.geometry.y = graph.snap(
            Math.round(y / graph.view.scale) - graph.view.translate.y - cell.geometry.height / 2
          );

          graph.model.beginUpdate();
          try {
            graph.addCell(clone);
          } finally {
            graph.model.endUpdate();
          }

          graph.setSelectionCell(clone);
          graph.scrollCellToVisible(clone);
          graph.startEditingAtCell(clone);

          if (ui.hoverIcons != null) {
            ui.hoverIcons.update(graph.view.getState(clone));
          }
        }

        ui.hideShapePicker();
      });
    });

    for (var i = 0; i < cells.length; i++) {
      addCell(cells[i]);
    }

    if (ui.hoverIcons != null) {
      ui.hoverIcons.reset();
    }

    graph.popupMenuHandler.hideMenu();
    graph.tooltipHandler.hideTooltip();
    this.hideCurrentMenu();
    this.hideShapePicker();

    this.shapePickerCallback = callback;
    this.shapePicker = div;
  }
};

/**
 * Creates a temporary graph instance for rendering off-screen content.
 */
EditorUi.prototype.getCellsForShapePicker = function (cell) {
  var createVertex = mxUtils.bind(this, function (style, w, h, value) {
    return this.editor.graph.createVertex(null, null, value || '', 0, 0, w || 120, h || 60, style, false);
  });

  return [
    cell != null
      ? this.editor.graph.cloneCell(cell)
      : createVertex('text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;', 40, 20, 'Text'),
    createVertex('whiteSpace=wrap;html=1;'),
    createVertex('ellipse;whiteSpace=wrap;html=1;', 120, 80),
    createVertex('rhombus;whiteSpace=wrap;html=1;', 80, 80),
    createVertex('shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;'),
    createVertex('shape=trapezoid;perimeter=trapezoidPerimeter;whiteSpace=wrap;html=1;fixedSize=1;', 120, 60),
    createVertex('shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;', 120, 80),
    createVertex('shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;', 120, 80),
    createVertex('shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;'),
    createVertex(
      'shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;',
      120,
      80
    ),
    createVertex('shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;', 80, 100),
    createVertex('triangle;whiteSpace=wrap;html=1;', 60, 80),
    createVertex('shape=document;whiteSpace=wrap;html=1;boundedLbl=1;', 120, 80),
    createVertex('shape=tape;whiteSpace=wrap;html=1;', 120, 100),
    createVertex('ellipse;shape=cloud;whiteSpace=wrap;html=1;', 120, 80),
    createVertex('shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;', 60, 80),
  ];
};

/**
 * Creates a temporary graph instance for rendering off-screen content.
 */
EditorUi.prototype.hideShapePicker = function (cancel) {
  if (this.shapePicker != null) {
    this.shapePicker.parentNode.removeChild(this.shapePicker);
    this.shapePicker = null;

    if (!cancel && this.shapePickerCallback != null) {
      this.shapePickerCallback();
    }

    this.shapePickerCallback = null;
  }
};

/**
 * Returns true if the given event should start editing. This implementation returns true.
 */
EditorUi.prototype.onKeyDown = function (evt) {
  var graph = this.editor.graph;

  // Tab selects next cell
  if (
    evt.which == 9 &&
    graph.isEnabled() &&
    !mxEvent.isAltDown(evt) &&
    (!graph.isEditing() || !mxEvent.isShiftDown(evt))
  ) {
    if (graph.isEditing()) {
      graph.stopEditing(false);
    } else {
      graph.selectCell(!mxEvent.isShiftDown(evt));
    }

    mxEvent.consume(evt);
  }
};

/**
 * Returns true if the given event should start editing. This implementation returns true.
 */
EditorUi.prototype.onKeyPress = function (evt) {
  var graph = this.editor.graph;

  // KNOWN: Focus does not work if label is empty in quirks mode
  if (
    this.isImmediateEditingEvent(evt) &&
    !graph.isEditing() &&
    !graph.isSelectionEmpty() &&
    evt.which !== 0 &&
    evt.which !== 27 &&
    !mxEvent.isAltDown(evt) &&
    !mxEvent.isControlDown(evt) &&
    !mxEvent.isMetaDown(evt)
  ) {
    graph.escape();
    graph.startEditing();

    // Workaround for FF where char is lost if cursor is placed before char
    if (mxClient.IS_FF) {
      var ce = graph.cellEditor;

      if (ce.textarea != null) {
        ce.textarea.innerHTML = String.fromCharCode(evt.which);

        // Moves cursor to end of textarea
        var range = document.createRange();
        range.selectNodeContents(ce.textarea);
        range.collapse(false);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
      }
    }
  }
};

/**
 * Returns true if the given event should start editing. This implementation returns true.
 */
EditorUi.prototype.isImmediateEditingEvent = function (evt) {
  return true;
};

/**
 * Private helper method.
 */
EditorUi.prototype.getCssClassForMarker = function (prefix, shape, marker, fill) {
  var result = '';

  if (shape == 'flexArrow') {
    result =
      marker != null && marker != mxConstants.NONE
        ? 'geSprite geSprite-' + prefix + 'blocktrans'
        : 'geSprite geSprite-noarrow';
  } else {
    // SVG marker sprites
    if (marker == 'box' || marker == 'halfCircle') {
      result = 'geSprite geSvgSprite geSprite-' + marker + (prefix == 'end' ? ' geFlipSprite' : '');
    } else if (marker == mxConstants.ARROW_CLASSIC) {
      result = fill == '1' ? 'geSprite geSprite-' + prefix + 'classic' : 'geSprite geSprite-' + prefix + 'classictrans';
    } else if (marker == mxConstants.ARROW_CLASSIC_THIN) {
      result =
        fill == '1'
          ? 'geSprite geSprite-' + prefix + 'classicthin'
          : 'geSprite geSprite-' + prefix + 'classicthintrans';
    } else if (marker == mxConstants.ARROW_OPEN) {
      result = 'geSprite geSprite-' + prefix + 'open';
    } else if (marker == mxConstants.ARROW_OPEN_THIN) {
      result = 'geSprite geSprite-' + prefix + 'openthin';
    } else if (marker == mxConstants.ARROW_BLOCK) {
      result = fill == '1' ? 'geSprite geSprite-' + prefix + 'block' : 'geSprite geSprite-' + prefix + 'blocktrans';
    } else if (marker == mxConstants.ARROW_BLOCK_THIN) {
      result =
        fill == '1' ? 'geSprite geSprite-' + prefix + 'blockthin' : 'geSprite geSprite-' + prefix + 'blockthintrans';
    } else if (marker == mxConstants.ARROW_OVAL) {
      result = fill == '1' ? 'geSprite geSprite-' + prefix + 'oval' : 'geSprite geSprite-' + prefix + 'ovaltrans';
    } else if (marker == mxConstants.ARROW_DIAMOND) {
      result = fill == '1' ? 'geSprite geSprite-' + prefix + 'diamond' : 'geSprite geSprite-' + prefix + 'diamondtrans';
    } else if (marker == mxConstants.ARROW_DIAMOND_THIN) {
      result =
        fill == '1'
          ? 'geSprite geSprite-' + prefix + 'thindiamond'
          : 'geSprite geSprite-' + prefix + 'thindiamondtrans';
    } else if (marker == 'openAsync') {
      result = 'geSprite geSprite-' + prefix + 'openasync';
    } else if (marker == 'dash') {
      result = 'geSprite geSprite-' + prefix + 'dash';
    } else if (marker == 'cross') {
      result = 'geSprite geSprite-' + prefix + 'cross';
    } else if (marker == 'async') {
      result = fill == '1' ? 'geSprite geSprite-' + prefix + 'async' : 'geSprite geSprite-' + prefix + 'asynctrans';
    } else if (marker == 'circle' || marker == 'circlePlus') {
      result =
        fill == '1' || marker == 'circle'
          ? 'geSprite geSprite-' + prefix + 'circle'
          : 'geSprite geSprite-' + prefix + 'circleplus';
    } else if (marker == 'ERone') {
      result = 'geSprite geSprite-' + prefix + 'erone';
    } else if (marker == 'ERmandOne') {
      result = 'geSprite geSprite-' + prefix + 'eronetoone';
    } else if (marker == 'ERmany') {
      result = 'geSprite geSprite-' + prefix + 'ermany';
    } else if (marker == 'ERoneToMany') {
      result = 'geSprite geSprite-' + prefix + 'eronetomany';
    } else if (marker == 'ERzeroToOne') {
      result = 'geSprite geSprite-' + prefix + 'eroneopt';
    } else if (marker == 'ERzeroToMany') {
      result = 'geSprite geSprite-' + prefix + 'ermanyopt';
    } else {
      result = 'geSprite geSprite-noarrow';
    }
  }

  return result;
};

/**
 * Overridden in Menus.js
 */
EditorUi.prototype.createMenus = function () {
  return null;
};

/**
 * Hook for allowing selection and context menu for certain events.
 */
EditorUi.prototype.updatePasteActionStates = function () {
  var graph = this.editor.graph;
  var paste = this.actions.get('paste');
  var pasteHere = this.actions.get('pasteHere');

  paste.setEnabled(
    this.editor.graph.cellEditor.isContentEditing() ||
      (!mxClipboard.isEmpty() && graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
  );
  pasteHere.setEnabled(paste.isEnabled());
};

/**
 * Hook for allowing selection and context menu for certain events.
 */
EditorUi.prototype.initClipboard = function () {
  var ui = this;

  var mxClipboardCut = mxClipboard.cut;
  mxClipboard.cut = function (graph) {
    if (graph.cellEditor.isContentEditing()) {
      document.execCommand('cut', false, null);
    } else {
      mxClipboardCut.apply(this, arguments);
    }

    ui.updatePasteActionStates();
  };

  var mxClipboardCopy = mxClipboard.copy;
  mxClipboard.copy = function (graph) {
    var result = null;

    if (graph.cellEditor.isContentEditing()) {
      document.execCommand('copy', false, null);
    } else {
      result = result || graph.getSelectionCells();
      result = graph.getExportableCells(graph.model.getTopmostCells(result));

      var cloneMap = new Object();
      var lookup = graph.createCellLookup(result);
      var clones = graph.cloneCells(result, null, cloneMap);

      // Uses temporary model to force new IDs to be assigned
      // to avoid having to carry over the mapping from object
      // ID to cell ID to the paste operation
      var model = new mxGraphModel();
      var parent = model.getChildAt(model.getRoot(), 0);

      for (var i = 0; i < clones.length; i++) {
        model.add(parent, clones[i]);

        // Checks for orphaned relative children and makes absolute
        var state = graph.view.getState(result[i]);

        if (state != null) {
          var geo = graph.getCellGeometry(clones[i]);

          if (
            geo != null &&
            geo.relative &&
            !model.isEdge(result[i]) &&
            lookup[mxObjectIdentity.get(model.getParent(result[i]))] == null
          ) {
            geo.offset = null;
            geo.relative = false;
            geo.x = state.x / state.view.scale - state.view.translate.x;
            geo.y = state.y / state.view.scale - state.view.translate.y;
          }
        }
      }

      graph.updateCustomLinks(graph.createCellMapping(cloneMap, lookup), clones);

      mxClipboard.insertCount = 1;
      mxClipboard.setCells(clones);
    }

    ui.updatePasteActionStates();

    return result;
  };

  var mxClipboardPaste = mxClipboard.paste;
  mxClipboard.paste = function (graph) {
    var result = null;

    if (graph.cellEditor.isContentEditing()) {
      document.execCommand('paste', false, null);
    } else {
      result = mxClipboardPaste.apply(this, arguments);
    }

    ui.updatePasteActionStates();

    return result;
  };

  // Overrides cell editor to update paste action state
  var cellEditorStartEditing = this.editor.graph.cellEditor.startEditing;

  this.editor.graph.cellEditor.startEditing = function () {
    cellEditorStartEditing.apply(this, arguments);
    ui.updatePasteActionStates();
  };

  var cellEditorStopEditing = this.editor.graph.cellEditor.stopEditing;

  this.editor.graph.cellEditor.stopEditing = function (cell, trigger) {
    cellEditorStopEditing.apply(this, arguments);
    ui.updatePasteActionStates();
  };

  this.updatePasteActionStates();
};

/**
 * Delay between zoom steps when not using preview.
 */
EditorUi.prototype.lazyZoomDelay = 20;

/**
 * Delay before update of DOM when using preview.
 */
EditorUi.prototype.wheelZoomDelay = 400;

/**
 * Delay before update of DOM when using preview.
 */
EditorUi.prototype.buttonZoomDelay = 600;

/**
 * Initializes the infinite canvas.
 */
EditorUi.prototype.initCanvas = function () {
  // Initial page layout view, scrollBuffer and timer-based scrolling
  var graph = this.editor.graph;
  graph.timerAutoScroll = true;

  /**
   * Returns the padding for pages in page view with scrollbars.
   */
  graph.getPagePadding = function () {
    return new mxPoint(
      Math.max(0, Math.round((graph.container.offsetWidth - 34) / graph.view.scale)),
      Math.max(0, Math.round((graph.container.offsetHeight - 34) / graph.view.scale))
    );
  };

  // Fits the number of background pages to the graph
  graph.view.getBackgroundPageBounds = function () {
    var layout = this.graph.getPageLayout();
    var page = this.graph.getPageSize();

    return new mxRectangle(
      this.scale * (this.translate.x + layout.x * page.width),
      this.scale * (this.translate.y + layout.y * page.height),
      this.scale * layout.width * page.width,
      this.scale * layout.height * page.height
    );
  };

  graph.getPreferredPageSize = function (bounds, width, height) {
    var pages = this.getPageLayout();
    var size = this.getPageSize();

    return new mxRectangle(0, 0, pages.width * size.width, pages.height * size.height);
  };

  // Scales pages/graph to fit available size
  var resize = null;
  var ui = this;

  if (this.editor.isChromelessView()) {
    resize = mxUtils.bind(this, function (autoscale, maxScale, cx, cy) {
      if (graph.container != null && !graph.isViewer()) {
        cx = cx != null ? cx : 0;
        cy = cy != null ? cy : 0;

        var bds = graph.pageVisible ? graph.view.getBackgroundPageBounds() : graph.getGraphBounds();
        var scroll = mxUtils.hasScrollbars(graph.container);
        var tr = graph.view.translate;
        var s = graph.view.scale;

        // Normalizes the bounds
        var b = mxRectangle.fromRectangle(bds);
        b.x = b.x / s - tr.x;
        b.y = b.y / s - tr.y;
        b.width /= s;
        b.height /= s;

        var st = graph.container.scrollTop;
        var sl = graph.container.scrollLeft;
        var sb = mxClient.IS_QUIRKS || document.documentMode >= 8 ? 20 : 14;

        if (document.documentMode == 8 || document.documentMode == 9) {
          sb += 3;
        }

        var cw = graph.container.offsetWidth - sb;
        var ch = graph.container.offsetHeight - sb;

        var ns = autoscale ? Math.max(0.3, Math.min(maxScale || 1, cw / b.width)) : s;
        var dx = (cw - ns * b.width) / 2 / ns;
        var dy = this.lightboxVerticalDivider == 0 ? 0 : (ch - ns * b.height) / this.lightboxVerticalDivider / ns;

        if (scroll) {
          dx = Math.max(dx, 0);
          dy = Math.max(dy, 0);
        }

        if (scroll || bds.width < cw || bds.height < ch) {
          graph.view.scaleAndTranslate(ns, Math.floor(dx - b.x), Math.floor(dy - b.y));
          graph.container.scrollTop = (st * ns) / s;
          graph.container.scrollLeft = (sl * ns) / s;
        } else if (cx != 0 || cy != 0) {
          var t = graph.view.translate;
          graph.view.setTranslate(Math.floor(t.x + cx / s), Math.floor(t.y + cy / s));
        }
      }
    });

    // Hack to make function available to subclassers
    this.chromelessResize = resize;

    // Hook for subclassers for override
    this.chromelessWindowResize = mxUtils.bind(this, function () {
      this.chromelessResize(false);
    });

    // Removable resize listener
    var autoscaleResize = mxUtils.bind(this, function () {
      this.chromelessWindowResize(false);
    });

    mxEvent.addListener(window, 'resize', autoscaleResize);

    this.destroyFunctions.push(function () {
      mxEvent.removeListener(window, 'resize', autoscaleResize);
    });

    this.editor.addListener(
      'resetGraphView',
      mxUtils.bind(this, function () {
        this.chromelessResize(true);
      })
    );

    this.actions.get('zoomIn').funct = mxUtils.bind(this, function (evt) {
      graph.zoomIn();
      this.chromelessResize(false);
    });
    this.actions.get('zoomOut').funct = mxUtils.bind(this, function (evt) {
      graph.zoomOut();
      this.chromelessResize(false);
    });

    // Creates toolbar for viewer - do not use CSS here
    // as this may be used in a viewer that has no CSS
    if (urlParams['toolbar'] != '0') {
      var toolbarConfig = JSON.parse(decodeURIComponent(urlParams['toolbar-config'] || '{}'));

      this.chromelessToolbar = document.createElement('div');
      this.chromelessToolbar.style.position = 'fixed';
      this.chromelessToolbar.style.overflow = 'hidden';
      this.chromelessToolbar.style.boxSizing = 'border-box';
      this.chromelessToolbar.style.whiteSpace = 'nowrap';
      this.chromelessToolbar.style.backgroundColor = '#000000';
      this.chromelessToolbar.style.padding = '10px 10px 8px 10px';
      this.chromelessToolbar.style.left = graph.isViewer() ? '0' : '50%';

      if (!mxClient.IS_VML) {
        mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'borderRadius', '20px');
        mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'transition', 'opacity 600ms ease-in-out');
      }

      var updateChromelessToolbarPosition = mxUtils.bind(this, function () {
        var css = mxUtils.getCurrentStyle(graph.container);

        if (graph.isViewer()) {
          this.chromelessToolbar.style.top = '0';
        } else {
          this.chromelessToolbar.style.bottom =
            (css != null ? parseInt(css['margin-bottom'] || 0) : 0) +
            (this.tabContainer != null ? 20 + parseInt(this.tabContainer.style.height) : 20) +
            'px';
        }
      });

      this.editor.addListener('resetGraphView', updateChromelessToolbarPosition);
      updateChromelessToolbarPosition();

      var btnCount = 0;

      var addButton = mxUtils.bind(this, function (fn, imgSrc, tip) {
        btnCount++;

        var a = document.createElement('span');
        a.style.paddingLeft = '8px';
        a.style.paddingRight = '8px';
        a.style.cursor = 'pointer';
        mxEvent.addListener(a, 'click', fn);

        if (tip != null) {
          a.setAttribute('title', tip);
        }

        var img = document.createElement('img');
        img.setAttribute('border', '0');
        img.setAttribute('src', imgSrc);

        a.appendChild(img);
        this.chromelessToolbar.appendChild(a);

        return a;
      });

      if (toolbarConfig.backBtn != null) {
        addButton(
          mxUtils.bind(this, function (evt) {
            window.location.href = toolbarConfig.backBtn.url;
            mxEvent.consume(evt);
          }),
          Editor.backLargeImage,
          mxResources.get('back', null, 'Back')
        );
      }

      if (this.isPagesEnabled()) {
        var prevButton = addButton(
          mxUtils.bind(this, function (evt) {
            this.actions.get('previousPage').funct();
            mxEvent.consume(evt);
          }),
          Editor.previousLargeImage,
          mxResources.get('previousPage')
        );

        var pageInfo = document.createElement('div');
        pageInfo.style.display = 'inline-block';
        pageInfo.style.verticalAlign = 'top';
        pageInfo.style.fontFamily = 'Helvetica,Arial';
        pageInfo.style.marginTop = '8px';
        pageInfo.style.fontSize = '14px';
        pageInfo.style.color = '#ffffff';
        this.chromelessToolbar.appendChild(pageInfo);

        var nextButton = addButton(
          mxUtils.bind(this, function (evt) {
            this.actions.get('nextPage').funct();
            mxEvent.consume(evt);
          }),
          Editor.nextLargeImage,
          mxResources.get('nextPage')
        );

        var updatePageInfo = mxUtils.bind(this, function () {
          if (this.pages != null && this.pages.length > 1 && this.currentPage != null) {
            pageInfo.innerHTML = '';
            mxUtils.write(pageInfo, mxUtils.indexOf(this.pages, this.currentPage) + 1 + ' / ' + this.pages.length);
          }
        });

        prevButton.style.paddingLeft = '0px';
        prevButton.style.paddingRight = '4px';
        nextButton.style.paddingLeft = '4px';
        nextButton.style.paddingRight = '0px';

        var updatePageButtons = mxUtils.bind(this, function () {
          if (this.pages != null && this.pages.length > 1 && this.currentPage != null) {
            nextButton.style.display = '';
            prevButton.style.display = '';
            pageInfo.style.display = 'inline-block';
          } else {
            nextButton.style.display = 'none';
            prevButton.style.display = 'none';
            pageInfo.style.display = 'none';
          }

          updatePageInfo();
        });

        this.editor.addListener('resetGraphView', updatePageButtons);
        this.editor.addListener('pageSelected', updatePageInfo);
      }

      addButton(
        mxUtils.bind(this, function (evt) {
          this.actions.get('zoomOut').funct();
          mxEvent.consume(evt);
        }),
        Editor.zoomOutLargeImage,
        mxResources.get('zoomOut') + ' (Alt+Mousewheel)'
      );

      addButton(
        mxUtils.bind(this, function (evt) {
          this.actions.get('zoomIn').funct();
          mxEvent.consume(evt);
        }),
        Editor.zoomInLargeImage,
        mxResources.get('zoomIn') + ' (Alt+Mousewheel)'
      );

      addButton(
        mxUtils.bind(this, function (evt) {
          if (graph.isLightboxView()) {
            if (graph.view.scale == 1) {
              this.lightboxFit();
            } else {
              graph.zoomTo(1);
            }

            this.chromelessResize(false);
          } else {
            this.chromelessResize(true);
          }

          mxEvent.consume(evt);
        }),
        Editor.actualSizeLargeImage,
        mxResources.get('fit')
      );

      // Changes toolbar opacity on hover
      var fadeThread = null;
      var fadeThread2 = null;

      var fadeOut = mxUtils.bind(this, function (delay) {
        if (fadeThread != null) {
          window.clearTimeout(fadeThread);
          fadeThread = null;
        }

        if (fadeThread2 != null) {
          window.clearTimeout(fadeThread2);
          fadeThread2 = null;
        }

        fadeThread = window.setTimeout(
          mxUtils.bind(this, function () {
            mxUtils.setOpacity(this.chromelessToolbar, 0);
            fadeThread = null;

            fadeThread2 = window.setTimeout(
              mxUtils.bind(this, function () {
                this.chromelessToolbar.style.display = 'none';
                fadeThread2 = null;
              }),
              600
            );
          }),
          delay || 200
        );
      });

      var fadeIn = mxUtils.bind(this, function (opacity) {
        if (fadeThread != null) {
          window.clearTimeout(fadeThread);
          fadeThread = null;
        }

        if (fadeThread2 != null) {
          window.clearTimeout(fadeThread2);
          fadeThread2 = null;
        }

        this.chromelessToolbar.style.display = '';
        mxUtils.setOpacity(this.chromelessToolbar, opacity || 30);
      });

      if (urlParams['layers'] == '1') {
        this.layersDialog = null;

        var layersButton = addButton(
          mxUtils.bind(this, function (evt) {
            if (this.layersDialog != null) {
              this.layersDialog.parentNode.removeChild(this.layersDialog);
              this.layersDialog = null;
            } else {
              this.layersDialog = graph.createLayersDialog();

              mxEvent.addListener(
                this.layersDialog,
                'mouseleave',
                mxUtils.bind(this, function () {
                  this.layersDialog.parentNode.removeChild(this.layersDialog);
                  this.layersDialog = null;
                })
              );

              var r = layersButton.getBoundingClientRect();

              mxUtils.setPrefixedStyle(this.layersDialog.style, 'borderRadius', '5px');
              this.layersDialog.style.position = 'fixed';
              this.layersDialog.style.fontFamily = 'Helvetica,Arial';
              this.layersDialog.style.backgroundColor = '#000000';
              this.layersDialog.style.width = '160px';
              this.layersDialog.style.padding = '4px 2px 4px 2px';
              this.layersDialog.style.color = '#ffffff';
              mxUtils.setOpacity(this.layersDialog, 70);
              this.layersDialog.style.left = r.left + 'px';
              this.layersDialog.style.bottom =
                parseInt(this.chromelessToolbar.style.bottom) + this.chromelessToolbar.offsetHeight + 4 + 'px';

              // Puts the dialog on top of the container z-index
              var style = mxUtils.getCurrentStyle(this.editor.graph.container);
              this.layersDialog.style.zIndex = style.zIndex;

              document.body.appendChild(this.layersDialog);
            }

            mxEvent.consume(evt);
          }),
          Editor.layersLargeImage,
          mxResources.get('layers')
        );

        // Shows/hides layers button depending on content
        var model = graph.getModel();

        model.addListener(mxEvent.CHANGE, function () {
          layersButton.style.display = model.getChildCount(model.root) > 1 ? '' : 'none';
        });
      }

      if (urlParams['openInSameWin'] != '1') {
        this.addChromelessToolbarItems(addButton);
      }

      if (this.editor.editButtonLink != null || this.editor.editButtonFunc != null) {
        addButton(
          mxUtils.bind(this, function (evt) {
            if (this.editor.editButtonFunc != null) {
              this.editor.editButtonFunc();
            } else if (this.editor.editButtonLink == '_blank') {
              this.editor.editAsNew(this.getEditBlankXml());
            } else {
              graph.openLink(this.editor.editButtonLink, 'editWindow');
            }

            mxEvent.consume(evt);
          }),
          Editor.editLargeImage,
          mxResources.get('edit')
        );
      }

      if (this.lightboxToolbarActions != null) {
        for (var i = 0; i < this.lightboxToolbarActions.length; i++) {
          var lbAction = this.lightboxToolbarActions[i];
          addButton(lbAction.fn, lbAction.icon, lbAction.tooltip);
        }
      }

      if (toolbarConfig.refreshBtn != null) {
        addButton(
          mxUtils.bind(this, function (evt) {
            if (toolbarConfig.refreshBtn.url) {
              window.location.href = toolbarConfig.refreshBtn.url;
            } else {
              window.location.reload();
            }

            mxEvent.consume(evt);
          }),
          Editor.refreshLargeImage,
          mxResources.get('refresh', null, 'Refresh')
        );
      }

      if (toolbarConfig.fullscreenBtn != null && window.self !== window.top) {
        addButton(
          mxUtils.bind(this, function (evt) {
            if (toolbarConfig.fullscreenBtn.url) {
              graph.openLink(toolbarConfig.fullscreenBtn.url);
            } else {
              graph.openLink(window.location.href);
            }

            mxEvent.consume(evt);
          }),
          Editor.fullscreenLargeImage,
          mxResources.get('openInNewWindow', null, 'Open in New Window')
        );
      }

      if (
        (toolbarConfig.closeBtn && window.self === window.top) ||
        (graph.lightbox && (urlParams['close'] == '1' || this.container != document.body))
      ) {
        addButton(
          mxUtils.bind(this, function (evt) {
            if (urlParams['close'] == '1' || toolbarConfig.closeBtn) {
              window.close();
            } else {
              this.destroy();
              mxEvent.consume(evt);
            }
          }),
          Editor.closeLargeImage,
          mxResources.get('close') + ' (Escape)'
        );
      }

      // Initial state invisible
      this.chromelessToolbar.style.display = 'none';

      if (!graph.isViewer()) {
        mxUtils.setPrefixedStyle(this.chromelessToolbar.style, 'transform', 'translate(-50%,0)');
      }

      graph.container.appendChild(this.chromelessToolbar);

      mxEvent.addListener(
        graph.container,
        mxClient.IS_POINTER ? 'pointermove' : 'mousemove',
        mxUtils.bind(this, function (evt) {
          if (!mxEvent.isTouchEvent(evt)) {
            if (!mxEvent.isShiftDown(evt)) {
              fadeIn(30);
            }

            fadeOut();
          }
        })
      );

      mxEvent.addListener(this.chromelessToolbar, mxClient.IS_POINTER ? 'pointermove' : 'mousemove', function (evt) {
        mxEvent.consume(evt);
      });

      mxEvent.addListener(
        this.chromelessToolbar,
        'mouseenter',
        mxUtils.bind(this, function (evt) {
          if (!mxEvent.isShiftDown(evt)) {
            fadeIn(100);
          } else {
            fadeOut();
          }
        })
      );

      mxEvent.addListener(
        this.chromelessToolbar,
        'mousemove',
        mxUtils.bind(this, function (evt) {
          if (!mxEvent.isShiftDown(evt)) {
            fadeIn(100);
          } else {
            fadeOut();
          }

          mxEvent.consume(evt);
        })
      );

      mxEvent.addListener(
        this.chromelessToolbar,
        'mouseleave',
        mxUtils.bind(this, function (evt) {
          if (!mxEvent.isTouchEvent(evt)) {
            fadeIn(30);
          }
        })
      );

      // Shows/hides toolbar for touch devices
      var tol = graph.getTolerance();

      graph.addMouseListener({
        startX: 0,
        startY: 0,
        scrollLeft: 0,
        scrollTop: 0,
        mouseDown: function (sender, me) {
          this.startX = me.getGraphX();
          this.startY = me.getGraphY();
          this.scrollLeft = graph.container.scrollLeft;
          this.scrollTop = graph.container.scrollTop;
        },
        mouseMove: function (sender, me) {},
        mouseUp: function (sender, me) {
          if (mxEvent.isTouchEvent(me.getEvent())) {
            if (
              Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol &&
              Math.abs(this.scrollTop - graph.container.scrollTop) < tol &&
              Math.abs(this.startX - me.getGraphX()) < tol &&
              Math.abs(this.startY - me.getGraphY()) < tol
            ) {
              if (parseFloat(ui.chromelessToolbar.style.opacity || 0) > 0) {
                fadeOut();
              } else {
                fadeIn(30);
              }
            }
          }
        },
      });
    } // end if toolbar

    // Installs handling of highlight and handling links to relative links and anchors
    if (!this.editor.editable) {
      this.addChromelessClickHandler();
    }
  } else if (this.editor.extendCanvas) {
    /**
     * Guesses autoTranslate to avoid another repaint (see below).
     * Works if only the scale of the graph changes or if pages
     * are visible and the visible pages do not change.
     */
    var graphViewValidate = graph.view.validate;
    graph.view.validate = function () {
      if (this.graph.container != null && mxUtils.hasScrollbars(this.graph.container)) {
        var pad = this.graph.getPagePadding();
        var size = this.graph.getPageSize();

        // Updating scrollbars here causes flickering in quirks and is not needed
        // if zoom method is always used to set the current scale on the graph.
        var tx = this.translate.x;
        var ty = this.translate.y;
        this.translate.x = pad.x - (this.x0 || 0) * size.width;
        this.translate.y = pad.y - (this.y0 || 0) * size.height;
      }

      graphViewValidate.apply(this, arguments);
    };

    if (!graph.isViewer()) {
      var graphSizeDidChange = graph.sizeDidChange;

      graph.sizeDidChange = function () {
        if (this.container != null && mxUtils.hasScrollbars(this.container)) {
          var pages = this.getPageLayout();
          var pad = this.getPagePadding();
          var size = this.getPageSize();

          // Updates the minimum graph size
          var minw = Math.ceil(2 * pad.x + pages.width * size.width);
          var minh = Math.ceil(2 * pad.y + pages.height * size.height);

          var min = graph.minimumGraphSize;

          // LATER: Fix flicker of scrollbar size in IE quirks mode
          // after delayed call in window.resize event handler
          if (min == null || min.width != minw || min.height != minh) {
            graph.minimumGraphSize = new mxRectangle(0, 0, minw, minh);
          }

          // Updates auto-translate to include padding and graph size
          var dx = pad.x - pages.x * size.width;
          var dy = pad.y - pages.y * size.height;

          if (!this.autoTranslate && (this.view.translate.x != dx || this.view.translate.y != dy)) {
            this.autoTranslate = true;
            this.view.x0 = pages.x;
            this.view.y0 = pages.y;

            // NOTE: THIS INVOKES THIS METHOD AGAIN. UNFORTUNATELY THERE IS NO WAY AROUND THIS SINCE THE
            // BOUNDS ARE KNOWN AFTER THE VALIDATION AND SETTING THE TRANSLATE TRIGGERS A REVALIDATION.
            // SHOULD MOVE TRANSLATE/SCALE TO VIEW.
            var tx = graph.view.translate.x;
            var ty = graph.view.translate.y;
            graph.view.setTranslate(dx, dy);

            // LATER: Fix rounding errors for small zoom
            graph.container.scrollLeft += Math.round((dx - tx) * graph.view.scale);
            graph.container.scrollTop += Math.round((dy - ty) * graph.view.scale);

            this.autoTranslate = false;

            return;
          }

          graphSizeDidChange.apply(this, arguments);
        } else {
          // Fires event but does not invoke superclass
          this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', this.getGraphBounds()));
        }
      };
    }
  }

  // Accumulates the zoom factor while the rendering is taking place
  // so that not the complete sequence of zoom steps must be painted
  var bgGroup = graph.view.getBackgroundPane();
  var mainGroup = graph.view.getDrawPane();
  graph.cumulativeZoomFactor = 1;
  var updateZoomTimeout = null;
  var cursorPosition = null;
  var scrollPosition = null;
  var forcedZoom = null;
  var filter = null;

  var scheduleZoom = function (delay) {
    if (updateZoomTimeout != null) {
      window.clearTimeout(updateZoomTimeout);
    }

    window.setTimeout(function () {
      if (!graph.isMouseDown || forcedZoom) {
        updateZoomTimeout = window.setTimeout(
          mxUtils.bind(this, function () {
            if (graph.isFastZoomEnabled()) {
              // Transforms background page
              if (graph.view.backgroundPageShape != null && graph.view.backgroundPageShape.node != null) {
                mxUtils.setPrefixedStyle(graph.view.backgroundPageShape.node.style, 'transform-origin', null);
                mxUtils.setPrefixedStyle(graph.view.backgroundPageShape.node.style, 'transform', null);
              }

              // Transforms graph and background image
              mainGroup.style.transformOrigin = '';
              bgGroup.style.transformOrigin = '';

              // Workaround for no reset of transform in Safari
              if (mxClient.IS_SF) {
                mainGroup.style.transform = 'scale(1)';
                bgGroup.style.transform = 'scale(1)';

                window.setTimeout(function () {
                  mainGroup.style.transform = '';
                  bgGroup.style.transform = '';
                }, 0);
              } else {
                mainGroup.style.transform = '';
                bgGroup.style.transform = '';
              }

              // Shows interactive elements
              graph.view.getDecoratorPane().style.opacity = '';
              graph.view.getOverlayPane().style.opacity = '';
            }

            var sp = new mxPoint(graph.container.scrollLeft, graph.container.scrollTop);
            var offset = mxUtils.getOffset(graph.container);
            var prev = graph.view.scale;
            var dx = 0;
            var dy = 0;

            if (cursorPosition != null) {
              dx = graph.container.offsetWidth / 2 - cursorPosition.x + offset.x;
              dy = graph.container.offsetHeight / 2 - cursorPosition.y + offset.y;
            }

            graph.zoom(graph.cumulativeZoomFactor);
            var s = graph.view.scale;

            if (s != prev) {
              if (scrollPosition != null) {
                dx += sp.x - scrollPosition.x;
                dy += sp.y - scrollPosition.y;
              }

              if (resize != null) {
                ui.chromelessResize(
                  false,
                  null,
                  dx * (graph.cumulativeZoomFactor - 1),
                  dy * (graph.cumulativeZoomFactor - 1)
                );
              }

              if (mxUtils.hasScrollbars(graph.container) && (dx != 0 || dy != 0)) {
                graph.container.scrollLeft -= dx * (graph.cumulativeZoomFactor - 1);
                graph.container.scrollTop -= dy * (graph.cumulativeZoomFactor - 1);
              }
            }

            if (filter != null) {
              mainGroup.setAttribute('filter', filter);
            }

            graph.cumulativeZoomFactor = 1;
            updateZoomTimeout = null;
            scrollPosition = null;
            cursorPosition = null;
            forcedZoom = null;
            filter = null;
          }),
          delay != null ? delay : graph.isFastZoomEnabled() ? ui.wheelZoomDelay : ui.lazyZoomDelay
        );
      }
    }, 0);
  };

  graph.lazyZoom = function (zoomIn, ignoreCursorPosition, delay) {
    // TODO: Fix ignored cursor position if scrollbars are disabled
    ignoreCursorPosition = ignoreCursorPosition || !graph.scrollbars;

    if (ignoreCursorPosition) {
      cursorPosition = new mxPoint(
        graph.container.offsetLeft + graph.container.clientWidth / 2,
        graph.container.offsetTop + graph.container.clientHeight / 2
      );
    }

    // Switches to 5% zoom steps below 15%
    if (zoomIn) {
      if (this.view.scale * this.cumulativeZoomFactor <= 0.15) {
        this.cumulativeZoomFactor *= (this.view.scale + 0.05) / this.view.scale;
      } else {
        // Uses to 5% zoom steps for better grid rendering in webkit
        // and to avoid rounding errors for zoom steps
        this.cumulativeZoomFactor *= this.zoomFactor;
        this.cumulativeZoomFactor = Math.round(this.view.scale * this.cumulativeZoomFactor * 20) / 20 / this.view.scale;
      }
    } else {
      if (this.view.scale * this.cumulativeZoomFactor <= 0.15) {
        this.cumulativeZoomFactor *= (this.view.scale - 0.05) / this.view.scale;
      } else {
        // Uses to 5% zoom steps for better grid rendering in webkit
        // and to avoid rounding errors for zoom steps
        this.cumulativeZoomFactor /= this.zoomFactor;
        this.cumulativeZoomFactor = Math.round(this.view.scale * this.cumulativeZoomFactor * 20) / 20 / this.view.scale;
      }
    }

    this.cumulativeZoomFactor =
      Math.max(0.05, Math.min(this.view.scale * this.cumulativeZoomFactor, 160)) / this.view.scale;

    if (graph.isFastZoomEnabled()) {
      if (filter == null && mainGroup.getAttribute('filter') != '') {
        filter = mainGroup.getAttribute('filter');
        mainGroup.removeAttribute('filter');
      }

      scrollPosition = new mxPoint(graph.container.scrollLeft, graph.container.scrollTop);

      var cx = ignoreCursorPosition
        ? graph.container.scrollLeft + graph.container.clientWidth / 2
        : cursorPosition.x + graph.container.scrollLeft - graph.container.offsetLeft;
      var cy = ignoreCursorPosition
        ? graph.container.scrollTop + graph.container.clientHeight / 2
        : cursorPosition.y + graph.container.scrollTop - graph.container.offsetTop;
      mainGroup.style.transformOrigin = cx + 'px ' + cy + 'px';
      mainGroup.style.transform = 'scale(' + this.cumulativeZoomFactor + ')';
      bgGroup.style.transformOrigin = cx + 'px ' + cy + 'px';
      bgGroup.style.transform = 'scale(' + this.cumulativeZoomFactor + ')';

      if (graph.view.backgroundPageShape != null && graph.view.backgroundPageShape.node != null) {
        var page = graph.view.backgroundPageShape.node;

        mxUtils.setPrefixedStyle(
          page.style,
          'transform-origin',
          (ignoreCursorPosition
            ? graph.container.clientWidth / 2 + graph.container.scrollLeft - page.offsetLeft + 'px'
            : cursorPosition.x + graph.container.scrollLeft - page.offsetLeft - graph.container.offsetLeft + 'px') +
            ' ' +
            (ignoreCursorPosition
              ? graph.container.clientHeight / 2 + graph.container.scrollTop - page.offsetTop + 'px'
              : cursorPosition.y + graph.container.scrollTop - page.offsetTop - graph.container.offsetTop + 'px')
        );
        mxUtils.setPrefixedStyle(page.style, 'transform', 'scale(' + this.cumulativeZoomFactor + ')');
      }

      graph.view.getDecoratorPane().style.opacity = '0';
      graph.view.getOverlayPane().style.opacity = '0';

      if (ui.hoverIcons != null) {
        ui.hoverIcons.reset();
      }
    }

    scheduleZoom(delay);
  };

  // Holds back repaint until after mouse gestures
  mxEvent.addGestureListeners(
    graph.container,
    function (evt) {
      if (updateZoomTimeout != null) {
        window.clearTimeout(updateZoomTimeout);
      }
    },
    null,
    function (evt) {
      if (graph.cumulativeZoomFactor != 1) {
        scheduleZoom(0);
      }
    }
  );

  // Holds back repaint until scroll ends
  mxEvent.addListener(graph.container, 'scroll', function (evt) {
    if (updateZoomTimeout != null && !graph.isMouseDown && graph.cumulativeZoomFactor != 1) {
      scheduleZoom(0);
    }
  });

  mxEvent.addMouseWheelListener(
    mxUtils.bind(this, function (evt, up, force, cx, cy) {
      if (this.dialogs == null || this.dialogs.length == 0) {
        // Scrolls with scrollbars turned off
        if (!graph.scrollbars && !force && graph.isScrollWheelEvent(evt)) {
          var t = graph.view.getTranslate();
          var step = 40 / graph.view.scale;

          if (!mxEvent.isShiftDown(evt)) {
            graph.view.setTranslate(t.x, t.y + (up ? step : -step));
          } else {
            graph.view.setTranslate(t.x + (up ? -step : step), t.y);
          }
        } else if (force || graph.isZoomWheelEvent(evt)) {
          var source = mxEvent.getSource(evt);

          while (source != null) {
            if (source == graph.container) {
              graph.tooltipHandler.hideTooltip();
              cursorPosition =
                cx != null && cy != null
                  ? new mxPoint(cx, cy)
                  : new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
              forcedZoom = force;
              graph.lazyZoom(up);
              mxEvent.consume(evt);

              return false;
            }

            source = source.parentNode;
          }
        }
      }
    }),
    graph.container
  );

  // Uses fast zoom for pinch gestures on iOS
  graph.panningHandler.zoomGraph = function (evt) {
    graph.cumulativeZoomFactor = evt.scale;
    graph.lazyZoom(evt.scale > 0, true);
    mxEvent.consume(evt);
  };
};

/**
 * Creates a temporary graph instance for rendering off-screen content.
 */
EditorUi.prototype.addChromelessToolbarItems = function (addButton) {
  addButton(
    mxUtils.bind(this, function (evt) {
      this.actions.get('print').funct();
      mxEvent.consume(evt);
    }),
    Editor.printLargeImage,
    mxResources.get('print')
  );
};

/**
 * Creates a temporary graph instance for rendering off-screen content.
 */
EditorUi.prototype.isPagesEnabled = function () {
  return this.editor.editable || urlParams['hide-pages'] != '1';
};

/**
 * Creates a temporary graph instance for rendering off-screen content.
 */
EditorUi.prototype.createTemporaryGraph = function (stylesheet) {
  var graph = new Graph(document.createElement('div'));
  graph.stylesheet.styles = mxUtils.clone(stylesheet.styles);
  graph.resetViewOnRootChange = false;
  graph.setConnectable(false);
  graph.gridEnabled = false;
  graph.autoScroll = false;
  graph.setTooltips(false);
  graph.setEnabled(false);

  // Container must be in the DOM for correct HTML rendering
  graph.container.style.visibility = 'hidden';
  graph.container.style.position = 'absolute';
  graph.container.style.overflow = 'hidden';
  graph.container.style.height = '1px';
  graph.container.style.width = '1px';

  return graph;
};

/**
 *
 */
EditorUi.prototype.addChromelessClickHandler = function () {
  var hl = urlParams['highlight'];

  // Adds leading # for highlight color code
  if (hl != null && hl.length > 0) {
    hl = '#' + hl;
  }

  this.editor.graph.addClickHandler(hl);
};

/**
 *
 */
EditorUi.prototype.toggleFormatPanel = function (visible) {
  visible = visible != null ? visible : this.formatWidth == 0;

  if (this.format != null) {
    this.formatWidth = visible ? 240 : 0;
    this.formatContainer.style.display = visible ? '' : 'none';
    this.refresh();
    this.format.refresh();
    this.fireEvent(new mxEventObject('formatWidthChanged'));
  }
};

/**
 * Adds support for placeholders in labels.
 */
EditorUi.prototype.lightboxFit = function (maxHeight) {
  if (this.isDiagramEmpty()) {
    this.editor.graph.view.setScale(1);
  } else {
    var p = urlParams['border'];
    var border = 60;

    if (p != null) {
      border = parseInt(p);
    }

    // LATER: Use initial graph bounds to avoid rounding errors
    this.editor.graph.maxFitScale = this.lightboxMaxFitScale;
    this.editor.graph.fit(border, null, null, null, null, null, maxHeight);
    this.editor.graph.maxFitScale = null;
  }
};

/**
 * Translates this point by the given vector.
 *
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
EditorUi.prototype.isDiagramEmpty = function () {
  var model = this.editor.graph.getModel();

  return model.getChildCount(model.root) == 1 && model.getChildCount(model.getChildAt(model.root, 0)) == 0;
};

/**
 * Hook for allowing selection and context menu for certain events.
 */
EditorUi.prototype.isSelectionAllowed = function (evt) {
  return (
    mxEvent.getSource(evt).nodeName == 'SELECT' ||
    (mxEvent.getSource(evt).nodeName == 'INPUT' && mxUtils.isAncestorNode(this.formatContainer, mxEvent.getSource(evt)))
  );
};

/**
 * Installs dialog if browser window is closed without saving
 * This must be disabled during save and image export.
 */
EditorUi.prototype.addBeforeUnloadListener = function () {
  // Installs dialog if browser window is closed without saving
  // This must be disabled during save and image export
  window.onbeforeunload = mxUtils.bind(this, function () {
    if (!this.editor.isChromelessView()) {
      // return this.onBeforeUnload();
    }
  });
};

/**
 * Sets the onbeforeunload for the application
 */
EditorUi.prototype.onBeforeUnload = function () {
  if (this.editor.modified) {
    return mxResources.get('allChangesLost');
  }
};

/**
 * Opens the current diagram via the window.opener if one exists.
 */
EditorUi.prototype.open = function () {
  // Cross-domain window access is not allowed in FF, so if we
  // were opened from another domain then this will fail.
  try {
    if (window.opener != null && window.opener.openFile != null) {
      window.opener.openFile.setConsumer(
        mxUtils.bind(this, function (xml, filename) {
          try {
            var doc = mxUtils.parseXml(xml);
            this.editor.setGraphXml(doc.documentElement);
            this.editor.setModified(false);
            this.editor.undoManager.clear();

            if (filename != null) {
              this.editor.setFilename(filename);
              this.updateDocumentTitle();
            }

            return;
          } catch (e) {
            mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
          }
        })
      );
    }
  } catch (e) {
    // ignore
  }

  // Fires as the last step if no file was loaded
  this.editor.graph.view.validate();

  // Required only in special cases where an initial file is opened
  // and the minimumGraphSize changes and CSS must be updated.
  this.editor.graph.sizeDidChange();
  this.editor.fireEvent(new mxEventObject('resetGraphView'));
};

/**
 * Shows the given popup menu.
 */
EditorUi.prototype.showPopupMenu = function (fn, x, y, evt) {
  this.editor.graph.popupMenuHandler.hideMenu();

  var menu = new mxPopupMenu(fn, this.container);
  menu.div.className += ' geMenubarMenu';
  menu.smartSeparators = true;
  menu.showDisabled = true;
  menu.autoExpand = true;

  // Disables autoexpand and destroys menu when hidden
  menu.hideMenu = mxUtils.bind(this, function () {
    mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
    menu.destroy();
  });

  menu.popup(x, y, null, evt);

  // Allows hiding by clicking on document
  this.setCurrentMenu(menu);
};

/**
 * Sets the current menu and element.
 */
EditorUi.prototype.setCurrentMenu = function (menu, elt) {
  this.currentMenuElt = elt;
  this.currentMenu = menu;
};

/**
 * Resets the current menu and element.
 */
EditorUi.prototype.resetCurrentMenu = function () {
  this.currentMenuElt = null;
  this.currentMenu = null;
};

/**
 * Hides and destroys the current menu.
 */
EditorUi.prototype.hideCurrentMenu = function () {
  if (this.currentMenu != null) {
    this.currentMenu.hideMenu();
    this.resetCurrentMenu();
  }
};

/**
 * Updates the document title.
 */
EditorUi.prototype.updateDocumentTitle = function () {
  var title = this.editor.getOrCreateFilename();

  if (this.editor.appName != null) {
    title += ' - ' + this.editor.appName;
  }

  document.title = title;
};

/**
 * Updates the document title.
 */
EditorUi.prototype.createHoverIcons = function () {
  return new HoverIcons(this.editor.graph);
};

/**
 * Returns the URL for a copy of this editor with no state.
 */
EditorUi.prototype.redo = function () {
  try {
    var graph = this.editor.graph;

    if (graph.isEditing()) {
      document.execCommand('redo', false, null);
    } else {
      this.editor.undoManager.redo();
    }
  } catch (e) {
    // ignore all errors
  }
};

/**
 * Returns the URL for a copy of this editor with no state.
 */
EditorUi.prototype.undo = function () {
  try {
    var graph = this.editor.graph;

    if (graph.isEditing()) {
      // Stops editing and executes undo on graph if native undo
      // does not affect current editing value
      var value = graph.cellEditor.textarea.innerHTML;
      document.execCommand('undo', false, null);

      if (value == graph.cellEditor.textarea.innerHTML) {
        graph.stopEditing(true);
        this.editor.undoManager.undo();
      }
    } else {
      this.editor.undoManager.undo();
    }
  } catch (e) {
    // ignore all errors
  }
};

/**
 * Returns the URL for a copy of this editor with no state.
 */
EditorUi.prototype.canRedo = function () {
  return this.editor.graph.isEditing() || this.editor.undoManager.canRedo();
};

/**
 * Returns the URL for a copy of this editor with no state.
 */
EditorUi.prototype.canUndo = function () {
  return this.editor.graph.isEditing() || this.editor.undoManager.canUndo();
};

/**
 *
 */
EditorUi.prototype.getEditBlankXml = function () {
  return mxUtils.getXml(this.editor.getGraphXml());
};

/**
 * Returns the URL for a copy of this editor with no state.
 */
EditorUi.prototype.getUrl = function (pathname) {
  var href = pathname != null ? pathname : window.location.pathname;
  var parms = href.indexOf('?') > 0 ? 1 : 0;

  // Removes template URL parameter for new blank diagram
  for (var key in urlParams) {
    if (parms == 0) {
      href += '?';
    } else {
      href += '&';
    }

    href += key + '=' + urlParams[key];
    parms++;
  }

  return href;
};

/**
 * Specifies if the graph has scrollbars.
 */
EditorUi.prototype.setScrollbars = function (value) {
  var graph = this.editor.graph;
  var prev = graph.container.style.overflow;
  graph.scrollbars = value;
  this.editor.updateGraphComponents();

  if (prev != graph.container.style.overflow) {
    graph.container.scrollTop = 0;
    graph.container.scrollLeft = 0;
    graph.view.scaleAndTranslate(1, 0, 0);
    this.resetScrollbars();
  }

  this.fireEvent(new mxEventObject('scrollbarsChanged'));
};

/**
 * Returns true if the graph has scrollbars.
 */
EditorUi.prototype.hasScrollbars = function () {
  return this.editor.graph.scrollbars;
};

/**
 * Resets the state of the scrollbars.
 */
EditorUi.prototype.resetScrollbars = function () {
  var graph = this.editor.graph;

  if (!this.editor.extendCanvas) {
    graph.container.scrollTop = 0;
    graph.container.scrollLeft = 0;

    if (!mxUtils.hasScrollbars(graph.container)) {
      graph.view.setTranslate(0, 0);
    }
  } else if (!this.editor.isChromelessView()) {
    if (mxUtils.hasScrollbars(graph.container)) {
      if (graph.pageVisible) {
        var pad = graph.getPagePadding();
        graph.container.scrollTop = Math.floor(pad.y - this.editor.initialTopSpacing) - 1;
        graph.container.scrollLeft =
          Math.floor(Math.min(pad.x, (graph.container.scrollWidth - graph.container.clientWidth) / 2)) - 1;

        // Scrolls graph to visible area
        var bounds = graph.getGraphBounds();

        if (bounds.width > 0 && bounds.height > 0) {
          if (bounds.x > graph.container.scrollLeft + graph.container.clientWidth * 0.9) {
            graph.container.scrollLeft = Math.min(bounds.x + bounds.width - graph.container.clientWidth, bounds.x - 10);
          }

          if (bounds.y > graph.container.scrollTop + graph.container.clientHeight * 0.9) {
            graph.container.scrollTop = Math.min(
              bounds.y + bounds.height - graph.container.clientHeight,
              bounds.y - 10
            );
          }
        }
      } else {
        var bounds = graph.getGraphBounds();
        var width = Math.max(bounds.width, graph.scrollTileSize.width * graph.view.scale);
        var height = Math.max(bounds.height, graph.scrollTileSize.height * graph.view.scale);
        graph.container.scrollTop = Math.floor(
          Math.max(0, bounds.y - Math.max(20, (graph.container.clientHeight - height) / 4))
        );
        graph.container.scrollLeft = Math.floor(
          Math.max(0, bounds.x - Math.max(0, (graph.container.clientWidth - width) / 2))
        );
      }
    } else {
      var b = mxRectangle.fromRectangle(
        graph.pageVisible ? graph.view.getBackgroundPageBounds() : graph.getGraphBounds()
      );
      var tr = graph.view.translate;
      var s = graph.view.scale;
      b.x = b.x / s - tr.x;
      b.y = b.y / s - tr.y;
      b.width /= s;
      b.height /= s;

      var dy = graph.pageVisible ? 0 : Math.max(0, (graph.container.clientHeight - b.height) / 4);

      graph.view.setTranslate(
        Math.floor(Math.max(0, (graph.container.clientWidth - b.width) / 2) - b.x + 2),
        Math.floor(dy - b.y + 1)
      );
    }
  }
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setPageVisible = function (value) {
  var graph = this.editor.graph;
  var hasScrollbars = mxUtils.hasScrollbars(graph.container);
  var tx = 0;
  var ty = 0;

  if (hasScrollbars) {
    tx = graph.view.translate.x * graph.view.scale - graph.container.scrollLeft;
    ty = graph.view.translate.y * graph.view.scale - graph.container.scrollTop;
  }

  graph.pageVisible = value;
  graph.pageBreaksVisible = value;
  graph.preferPageSize = value;
  graph.view.validateBackground();

  // Workaround for possible handle offset
  if (hasScrollbars) {
    var cells = graph.getSelectionCells();
    graph.clearSelection();
    graph.setSelectionCells(cells);
  }

  // Calls updatePageBreaks
  graph.sizeDidChange();

  if (hasScrollbars) {
    graph.container.scrollLeft = graph.view.translate.x * graph.view.scale - tx;
    graph.container.scrollTop = graph.view.translate.y * graph.view.scale - ty;
  }

  this.fireEvent(new mxEventObject('pageViewChanged'));
};

/**
 * Class: ChangeGridColor
 *
 * Undoable change to grid color.
 */
function ChangeGridColor(ui, color) {
  this.ui = ui;
  this.color = color;
}

/**
 * Executes selection of a new page.
 */
ChangeGridColor.prototype.execute = function () {
  var temp = this.ui.editor.graph.view.gridColor;
  this.ui.setGridColor(this.color);
  this.color = temp;
};

// Registers codec for ChangePageSetup
(function () {
  var codec = new mxObjectCodec(new ChangeGridColor(), ['ui']);

  mxCodecRegistry.register(codec);
})();

/**
 * Change types
 */
function ChangePageSetup(ui, color, image, format, pageScale) {
  this.ui = ui;
  this.color = color;
  this.previousColor = color;
  this.image = image;
  this.previousImage = image;
  this.format = format;
  this.previousFormat = format;
  this.pageScale = pageScale;
  this.previousPageScale = pageScale;

  // Needed since null are valid values for color and image
  this.ignoreColor = false;
  this.ignoreImage = false;
}

/**
 * Implementation of the undoable page rename.
 */
ChangePageSetup.prototype.execute = function () {
  var graph = this.ui.editor.graph;

  if (!this.ignoreColor) {
    this.color = this.previousColor;
    var tmp = graph.background;
    this.ui.setBackgroundColor(this.previousColor);
    this.previousColor = tmp;
  }

  if (!this.ignoreImage) {
    this.image = this.previousImage;
    var tmp = graph.backgroundImage;
    this.ui.setBackgroundImage(this.previousImage);
    this.previousImage = tmp;
  }

  if (this.previousFormat != null) {
    this.format = this.previousFormat;
    var tmp = graph.pageFormat;

    if (this.previousFormat.width != tmp.width || this.previousFormat.height != tmp.height) {
      this.ui.setPageFormat(this.previousFormat);
      this.previousFormat = tmp;
    }
  }

  if (this.foldingEnabled != null && this.foldingEnabled != this.ui.editor.graph.foldingEnabled) {
    this.ui.setFoldingEnabled(this.foldingEnabled);
    this.foldingEnabled = !this.foldingEnabled;
  }

  if (this.previousPageScale != null) {
    var currentPageScale = this.ui.editor.graph.pageScale;

    if (this.previousPageScale != currentPageScale) {
      this.ui.setPageScale(this.previousPageScale);
      this.previousPageScale = currentPageScale;
    }
  }
};

// Registers codec for ChangePageSetup
(function () {
  var codec = new mxObjectCodec(new ChangePageSetup(), [
    'ui',
    'previousColor',
    'previousImage',
    'previousFormat',
    'previousPageScale',
  ]);

  codec.afterDecode = function (dec, node, obj) {
    obj.previousColor = obj.color;
    obj.previousImage = obj.image;
    obj.previousFormat = obj.format;
    obj.previousPageScale = obj.pageScale;

    if (obj.foldingEnabled != null) {
      obj.foldingEnabled = !obj.foldingEnabled;
    }

    return obj;
  };

  mxCodecRegistry.register(codec);
})();

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setBackgroundColor = function (value) {
  this.editor.graph.background = value;
  this.editor.graph.view.validateBackground();

  this.fireEvent(new mxEventObject('backgroundColorChanged'));
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setFoldingEnabled = function (value) {
  this.editor.graph.foldingEnabled = value;
  this.editor.graph.view.revalidate();

  this.fireEvent(new mxEventObject('foldingEnabledChanged'));
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setPageFormat = function (value) {
  this.editor.graph.pageFormat = value;

  if (!this.editor.graph.pageVisible) {
    this.actions.get('pageView').funct();
  } else {
    this.editor.graph.view.validateBackground();
    this.editor.graph.sizeDidChange();
  }

  this.fireEvent(new mxEventObject('pageFormatChanged'));
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setPageScale = function (value) {
  this.editor.graph.pageScale = value;

  if (!this.editor.graph.pageVisible) {
    this.actions.get('pageView').funct();
  } else {
    this.editor.graph.view.validateBackground();
    this.editor.graph.sizeDidChange();
  }

  this.fireEvent(new mxEventObject('pageScaleChanged'));
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setGridColor = function (value) {
  this.editor.graph.view.gridColor = value;
  this.editor.graph.view.validateBackground();
  this.fireEvent(new mxEventObject('gridColorChanged'));
};

/**
 * Updates the states of the given undo/redo items.
 */
EditorUi.prototype.addUndoListener = function () {
  var undo = this.actions.get('undo');
  var redo = this.actions.get('redo');

  var undoMgr = this.editor.undoManager;

  var undoListener = mxUtils.bind(this, function () {
    undo.setEnabled(this.canUndo());
    redo.setEnabled(this.canRedo());
  });

  undoMgr.addListener(mxEvent.ADD, undoListener);
  undoMgr.addListener(mxEvent.UNDO, undoListener);
  undoMgr.addListener(mxEvent.REDO, undoListener);
  undoMgr.addListener(mxEvent.CLEAR, undoListener);

  // Overrides cell editor to update action states
  var cellEditorStartEditing = this.editor.graph.cellEditor.startEditing;

  this.editor.graph.cellEditor.startEditing = function () {
    cellEditorStartEditing.apply(this, arguments);
    undoListener();
  };

  var cellEditorStopEditing = this.editor.graph.cellEditor.stopEditing;

  this.editor.graph.cellEditor.stopEditing = function (cell, trigger) {
    cellEditorStopEditing.apply(this, arguments);
    undoListener();
  };

  // Updates the button states once
  undoListener();
};

/**
 * Updates the states of the given toolbar items based on the selection.
 */
EditorUi.prototype.updateActionStates = function () {
  var graph = this.editor.graph;
  var selected = !graph.isSelectionEmpty();
  var vertexSelected = false;
  var groupSelected = false;
  var edgeSelected = false;

  var cells = graph.getSelectionCells();

  if (cells != null) {
    for (var i = 0; i < cells.length; i++) {
      var cell = cells[i];

      if (graph.getModel().isEdge(cell)) {
        edgeSelected = true;
      }

      if (graph.getModel().isVertex(cell)) {
        vertexSelected = true;

        if (graph.getModel().getChildCount(cell) > 0 || graph.isContainer(cell)) {
          groupSelected = true;
        }
      }

      if (edgeSelected && vertexSelected) {
        break;
      }
    }
  }

  // Updates action states
  var actions = [
    'cut',
    'copy',
    'bold',
    'italic',
    'underline',
    'delete',
    'duplicate',
    'editStyle',
    'editTooltip',
    'editLink',
    'backgroundColor',
    'borderColor',
    'edit',
    'toFront',
    'toBack',
    'lockUnlock',
    'solid',
    'dashed',
    'pasteSize',
    'dotted',
    'fillColor',
    'gradientColor',
    'shadow',
    'fontColor',
    'formattedText',
    'rounded',
    'toggleRounded',
    'sharp',
    'strokeColor',
  ];

  for (var i = 0; i < actions.length; i++) {
    this.actions.get(actions[i]).setEnabled(selected);
  }

  this.actions.get('setAsDefaultStyle').setEnabled(graph.getSelectionCount() == 1);
  this.actions.get('clearWaypoints').setEnabled(!graph.isSelectionEmpty());
  this.actions.get('copySize').setEnabled(graph.getSelectionCount() == 1);
  this.actions.get('turn').setEnabled(!graph.isSelectionEmpty());
  this.actions.get('curved').setEnabled(edgeSelected);
  this.actions.get('rotation').setEnabled(vertexSelected);
  this.actions.get('wordWrap').setEnabled(vertexSelected);
  this.actions.get('autosize').setEnabled(vertexSelected);
  var oneVertexSelected = vertexSelected && graph.getSelectionCount() == 1;
  this.actions
    .get('group')
    .setEnabled(graph.getSelectionCount() > 1 || (oneVertexSelected && !graph.isContainer(graph.getSelectionCell())));
  this.actions.get('ungroup').setEnabled(groupSelected);
  this.actions
    .get('removeFromGroup')
    .setEnabled(oneVertexSelected && graph.getModel().isVertex(graph.getModel().getParent(graph.getSelectionCell())));

  // Updates menu states
  var state = graph.view.getState(graph.getSelectionCell());
  this.menus.get('navigation').setEnabled(selected || graph.view.currentRoot != null);
  this.actions
    .get('collapsible')
    .setEnabled(
      vertexSelected &&
        (graph.isContainer(graph.getSelectionCell()) || graph.model.getChildCount(graph.getSelectionCell()) > 0)
    );
  this.actions.get('home').setEnabled(graph.view.currentRoot != null);
  this.actions.get('exitGroup').setEnabled(graph.view.currentRoot != null);
  this.actions
    .get('enterGroup')
    .setEnabled(graph.getSelectionCount() == 1 && graph.isValidRoot(graph.getSelectionCell()));
  var foldable = graph.getSelectionCount() == 1 && graph.isCellFoldable(graph.getSelectionCell());
  this.actions.get('expand').setEnabled(foldable);
  this.actions.get('collapse').setEnabled(foldable);
  this.actions.get('editLink').setEnabled(graph.getSelectionCount() == 1);
  this.actions
    .get('openLink')
    .setEnabled(graph.getSelectionCount() == 1 && graph.getLinkForCell(graph.getSelectionCell()) != null);
  this.actions.get('guides').setEnabled(graph.isEnabled());
  this.actions.get('grid').setEnabled(!this.editor.chromeless || this.editor.editable);

  var unlocked = graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent());
  this.menus.get('layout').setEnabled(unlocked);
  this.menus.get('insert').setEnabled(unlocked);
  this.menus.get('direction').setEnabled(unlocked && vertexSelected);
  this.menus.get('align').setEnabled(unlocked && vertexSelected && graph.getSelectionCount() > 1);
  this.menus.get('distribute').setEnabled(unlocked && vertexSelected && graph.getSelectionCount() > 1);
  this.actions.get('selectVertices').setEnabled(unlocked);
  this.actions.get('selectEdges').setEnabled(unlocked);
  this.actions.get('selectAll').setEnabled(unlocked);
  this.actions.get('selectNone').setEnabled(unlocked);

  this.updatePasteActionStates();
};

EditorUi.prototype.zeroOffset = new mxPoint(0, 0);

EditorUi.prototype.getDiagramContainerOffset = function () {
  return this.zeroOffset;
};

/**
 * Refreshes the viewport.
 */
EditorUi.prototype.refresh = function (sizeDidChange) {
  sizeDidChange = sizeDidChange != null ? sizeDidChange : true;

  var quirks = mxClient.IS_IE && (document.documentMode == null || document.documentMode == 5);
  var w = this.container.clientWidth;
  var h = this.container.clientHeight;

  if (this.container == document.body) {
    w = document.body.clientWidth || document.documentElement.clientWidth;
    h = quirks
      ? document.body.clientHeight || document.documentElement.clientHeight
      : document.documentElement.clientHeight;
  }

  // Workaround for bug on iOS see
  // http://stackoverflow.com/questions/19012135/ios-7-ipad-safari-landscape-innerheight-outerheight-layout-issue
  // FIXME: Fix if footer visible
  var off = 0;

  if (mxClient.IS_IOS && !window.navigator.standalone) {
    if (window.innerHeight != document.documentElement.clientHeight) {
      off = document.documentElement.clientHeight - window.innerHeight;
      window.scrollTo(0, 0);
    }
  }

  var effHsplitPosition = Math.max(0, Math.min(this.hsplitPosition, w - this.splitSize - 20));
  var tmp = 0;

  if (this.menubar != null) {
    this.menubarContainer.style.height = this.menubarHeight + 'px';
    tmp += this.menubarHeight;
  }

  if (this.toolbar != null) {
    this.toolbarContainer.style.top = this.menubarHeight + 'px';
    this.toolbarContainer.style.height = this.toolbarHeight + 'px';
    tmp += this.toolbarHeight;
  }

  if (tmp > 0 && !mxClient.IS_QUIRKS) {
    tmp += 1;
  }

  var sidebarFooterHeight = 0;

  if (this.sidebarFooterContainer != null) {
    var bottom = this.footerHeight + off;
    sidebarFooterHeight = Math.max(0, Math.min(h - tmp - bottom, this.sidebarFooterHeight));
    this.sidebarFooterContainer.style.width = effHsplitPosition + 'px';
    this.sidebarFooterContainer.style.height = sidebarFooterHeight + 'px';
    this.sidebarFooterContainer.style.bottom = bottom + 'px';
  }

  var fw = this.format != null ? this.formatWidth : 0;
  this.sidebarContainer.style.top = tmp + 'px';
  this.sidebarContainer.style.width = effHsplitPosition + 'px';
  this.formatContainer.style.top = tmp + 'px';
  this.formatContainer.style.width = fw + 'px';
  this.formatContainer.style.display = this.format != null ? '' : 'none';

  var diagContOffset = this.getDiagramContainerOffset();
  var contLeft = this.hsplit.parentNode != null ? effHsplitPosition + this.splitSize : 0;
  this.diagramContainer.style.left = contLeft + diagContOffset.x + 'px';
  this.diagramContainer.style.top = tmp + diagContOffset.y + 'px';
  // this.footerContainer.style.height = this.footerHeight + 'px';
  this.hsplit.style.top = this.sidebarContainer.style.top;
  this.hsplit.style.bottom = this.footerHeight + off + 'px';
  this.hsplit.style.left = effHsplitPosition + 'px';
  // this.footerContainer.style.display = this.footerHeight == 0 ? 'none' : '';

  if (this.tabContainer != null) {
    this.tabContainer.style.left = contLeft + 'px';
  }

  if (quirks) {
    this.menubarContainer.style.width = w + 'px';
    this.toolbarContainer.style.width = this.menubarContainer.style.width;
    var sidebarHeight = Math.max(0, h - this.footerHeight - this.menubarHeight - this.toolbarHeight);
    this.sidebarContainer.style.height = sidebarHeight - sidebarFooterHeight + 'px';
    this.formatContainer.style.height = sidebarHeight + 'px';
    this.diagramContainer.style.width =
      this.hsplit.parentNode != null ? Math.max(0, w - effHsplitPosition - this.splitSize - fw) + 'px' : w + 'px';
    // this.footerContainer.style.width = this.menubarContainer.style.width;
    var diagramHeight = Math.max(0, h - this.footerHeight - this.menubarHeight - this.toolbarHeight);

    if (this.tabContainer != null) {
      this.tabContainer.style.width = this.diagramContainer.style.width;
      this.tabContainer.style.bottom = this.footerHeight + off + 'px';
      diagramHeight -= this.tabContainer.clientHeight;
    }

    this.diagramContainer.style.height = diagramHeight + 'px';
    this.hsplit.style.height = diagramHeight + 'px';
  } else {
    if (this.footerHeight > 0) {
      // this.footerContainer.style.bottom = off + 'px';
    }

    this.diagramContainer.style.right = fw + 'px';
    var th = 0;

    if (this.tabContainer != null) {
      this.tabContainer.style.bottom = this.footerHeight + off + 'px';
      this.tabContainer.style.right = this.diagramContainer.style.right;
      th = this.tabContainer.clientHeight;
    }

    this.sidebarContainer.style.bottom = this.footerHeight + sidebarFooterHeight + off + 'px';
    this.formatContainer.style.bottom = this.footerHeight + off + 'px';
    this.diagramContainer.style.bottom = this.footerHeight + off + th + 'px';
  }

  if (sizeDidChange) {
    this.editor.graph.sizeDidChange();
  }
};

/**
 * Creates the required containers.
 */
EditorUi.prototype.createTabContainer = function () {
  return null;
};

/**
 * Creates the required containers.
 */
EditorUi.prototype.createDivs = function () {
  this.menubarContainer = this.createDiv('geMenubarContainer');
  this.toolbarContainer = this.createDiv('geToolbarContainer');
  this.sidebarContainer = this.createDiv('geSidebarContainer');
  this.formatContainer = this.createDiv('geSidebarContainer geFormatContainer');
  this.diagramContainer = this.createDiv('geDiagramContainer');
  // this.footerContainer = this.createDiv('geFooterContainer');
  this.hsplit = this.createDiv('geHsplit');
  this.hsplit.setAttribute('title', mxResources.get('collapseExpand'));

  // Sets static style for containers
  this.menubarContainer.style.top = '0px';
  this.menubarContainer.style.left = '0px';
  this.menubarContainer.style.right = '0px';
  this.toolbarContainer.style.left = '0px';
  this.toolbarContainer.style.right = '0px';
  this.sidebarContainer.style.left = '0px';
  this.formatContainer.style.right = '0px';
  this.formatContainer.style.zIndex = '1';
  this.diagramContainer.style.right = (this.format != null ? this.formatWidth : 0) + 'px';
  // this.footerContainer.style.left = '0px';
  // this.footerContainer.style.right = '0px';
  // this.footerContainer.style.bottom = '0px';
  // this.footerContainer.style.zIndex = mxPopupMenu.prototype.zIndex - 2;
  this.hsplit.style.width = this.splitSize + 'px';
  this.sidebarFooterContainer = this.createSidebarFooterContainer();

  if (this.sidebarFooterContainer) {
    this.sidebarFooterContainer.style.left = '0px';
  }

  if (!this.editor.chromeless) {
    this.tabContainer = this.createTabContainer();
  } else {
    this.diagramContainer.style.border = 'none';
  }
};

/**
 * Hook for sidebar footer container. This implementation returns null.
 */
EditorUi.prototype.createSidebarFooterContainer = function () {
  return null;
};

/**
 * Creates the required containers.
 */
EditorUi.prototype.createUi = function () {
  // Creates menubar
  this.menubar = this.editor.chromeless ? null : this.menus.createMenubar(this.createDiv('geMenubar'));

  if (this.menubar != null) {
    this.menubarContainer.appendChild(this.menubar.container);
  }

  // Adds status bar in menubar
  if (this.menubar != null) {
    this.statusContainer = this.createStatusContainer();

    // Connects the status bar to the editor status
    this.editor.addListener(
      'statusChanged',
      mxUtils.bind(this, function () {
        this.setStatusText(this.editor.getStatus());
      })
    );

    this.setStatusText(this.editor.getStatus());
    this.menubar.container.appendChild(this.statusContainer);

    // Inserts into DOM
    this.container.appendChild(this.menubarContainer);
  }

  // Creates the sidebar
  this.sidebar = this.editor.chromeless ? null : this.createSidebar(this.sidebarContainer);

  if (this.sidebar != null) {
    this.container.appendChild(this.sidebarContainer);
  }

  // Creates the format sidebar
  this.format = this.editor.chromeless || !this.formatEnabled ? null : this.createFormat(this.formatContainer);

  if (this.format != null) {
    this.container.appendChild(this.formatContainer);
  }

  // Creates the footer
  // var footer = this.editor.chromeless ? null : this.createFooter();
  var footer = null;

  if (footer != null) {
    this.footerContainer.appendChild(footer);
    this.container.appendChild(this.footerContainer);
  }

  if (this.sidebar != null && this.sidebarFooterContainer) {
    this.container.appendChild(this.sidebarFooterContainer);
  }

  this.container.appendChild(this.diagramContainer);

  if (this.container != null && this.tabContainer != null) {
    this.container.appendChild(this.tabContainer);
  }

  // Creates toolbar
  this.toolbar = this.editor.chromeless ? null : this.createToolbar(this.createDiv('geToolbar'));

  if (this.toolbar != null) {
    this.toolbarContainer.appendChild(this.toolbar.container);
    this.container.appendChild(this.toolbarContainer);
  }

  // HSplit
  if (this.sidebar != null) {
    this.container.appendChild(this.hsplit);

    this.addSplitHandler(
      this.hsplit,
      true,
      0,
      mxUtils.bind(this, function (value) {
        this.hsplitPosition = value;
        this.refresh();
      })
    );
  }
};

/**
 * Creates a new toolbar for the given container.
 */
EditorUi.prototype.createStatusContainer = function () {
  var container = document.createElement('a');
  container.className = 'geItem geStatus';

  return container;
};

/**
 * Creates a new toolbar for the given container.
 */
EditorUi.prototype.setStatusText = function (value) {
  this.statusContainer.innerHTML = value;
};

/**
 * Creates a new toolbar for the given container.
 */
EditorUi.prototype.createToolbar = function (container) {
  return new Toolbar(this, container);
};

/**
 * Creates a new sidebar for the given container.
 */
EditorUi.prototype.createSidebar = function (container) {
  return new Sidebar(this, container);
};

/**
 * Creates a new sidebar for the given container.
 */
EditorUi.prototype.createFormat = function (container) {
  return new Format(this, container);
};

/**
 * Creates and returns a new footer.
 */
EditorUi.prototype.createFooter = function () {
  return this.createDiv('geFooter');
};

/**
 * Creates the actual toolbar for the toolbar container.
 */
EditorUi.prototype.createDiv = function (classname) {
  var elt = document.createElement('div');
  elt.className = classname;

  return elt;
};

/**
 * Updates the states of the given undo/redo items.
 */
EditorUi.prototype.addSplitHandler = function (elt, horizontal, dx, onChange) {
  var start = null;
  var initial = null;
  var ignoreClick = true;
  var last = null;

  // Disables built-in pan and zoom in IE10 and later
  if (mxClient.IS_POINTER) {
    elt.style.touchAction = 'none';
  }

  var getValue = mxUtils.bind(this, function () {
    var result = parseInt(horizontal ? elt.style.left : elt.style.bottom);

    // Takes into account hidden footer
    if (!horizontal) {
      result = result + dx - this.footerHeight;
    }

    return result;
  });

  function moveHandler(evt) {
    if (start != null) {
      var pt = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
      onChange(Math.max(0, initial + (horizontal ? pt.x - start.x : start.y - pt.y) - dx));
      mxEvent.consume(evt);

      if (initial != getValue()) {
        ignoreClick = true;
        last = null;
      }
    }
  }

  function dropHandler(evt) {
    moveHandler(evt);
    initial = null;
    start = null;
  }

  mxEvent.addGestureListeners(elt, function (evt) {
    start = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
    initial = getValue();
    ignoreClick = false;
    mxEvent.consume(evt);
  });

  mxEvent.addListener(
    elt,
    'click',
    mxUtils.bind(this, function (evt) {
      if (!ignoreClick && this.hsplitClickEnabled) {
        var next = last != null ? last - dx : 0;
        last = getValue();
        onChange(next);
        mxEvent.consume(evt);
      }
    })
  );

  mxEvent.addGestureListeners(document, null, moveHandler, dropHandler);

  this.destroyFunctions.push(function () {
    mxEvent.removeGestureListeners(document, null, moveHandler, dropHandler);
  });
};

/**
 * Translates this point by the given vector.
 *
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
EditorUi.prototype.handleError = function (resp, title, fn, invokeFnOnClose, notFoundMessage) {
  var e = resp != null && resp.error != null ? resp.error : resp;

  if (e != null || title != null) {
    var msg = mxUtils.htmlEntities(mxResources.get('unknownError'));
    var btn = mxResources.get('ok');
    title = title != null ? title : mxResources.get('error');

    if (e != null && e.message != null) {
      msg = mxUtils.htmlEntities(e.message);
    }

    this.showError(title, msg, btn, fn, null, null, null, null, null, null, null, null, invokeFnOnClose ? fn : null);
  } else if (fn != null) {
    fn();
  }
};

/**
 * Translates this point by the given vector.
 *
 * @param {number} dx X-coordinate of the translation.
 * @param {number} dy Y-coordinate of the translation.
 */
EditorUi.prototype.showError = function (title, msg, btn, fn, retry, btn2, fn2, btn3, fn3, w, h, hide, onClose) {
  var dlg = new ErrorDialog(this, title, msg, btn || mxResources.get('ok'), fn, retry, btn2, fn2, hide, btn3, fn3);
  var lines = Math.ceil(msg != null ? msg.length / 50 : 1);
  this.showDialog(dlg.container, w || 340, h || 100 + lines * 20, true, false, onClose);
  dlg.init();
};

/**
 * Displays a print dialog.
 */
EditorUi.prototype.showDialog = function (
  elt,
  w,
  h,
  modal,
  closable,
  onClose,
  noScroll,
  transparent,
  onResize,
  ignoreBgClick
) {
  this.editor.graph.tooltipHandler.hideTooltip();

  if (this.dialogs == null) {
    this.dialogs = [];
  }

  this.dialog = new Dialog(this, elt, w, h, modal, closable, onClose, noScroll, transparent, onResize, ignoreBgClick);
  this.dialogs.push(this.dialog);
};

/**
 * Displays a print dialog.
 */
EditorUi.prototype.hideDialog = function (cancel, isEsc) {
  if (this.dialogs != null && this.dialogs.length > 0) {
    var dlg = this.dialogs.pop();

    if (dlg.close(cancel, isEsc) == false) {
      //add the dialog back if dialog closing is cancelled
      this.dialogs.push(dlg);
      return;
    }

    this.dialog = this.dialogs.length > 0 ? this.dialogs[this.dialogs.length - 1] : null;
    this.editor.fireEvent(new mxEventObject('hideDialog'));

    if (this.dialog == null && this.editor.graph.container.style.visibility != 'hidden') {
      window.setTimeout(
        mxUtils.bind(this, function () {
          if (this.editor.graph.isEditing() && this.editor.graph.cellEditor.textarea != null) {
            this.editor.graph.cellEditor.textarea.focus();
          } else {
            mxUtils.clearSelection();
            this.editor.graph.container.focus();
          }
        }),
        0
      );
    }
  }
};

/**
 * Handles ctrl+enter keystroke to clone cells.
 */
EditorUi.prototype.ctrlEnter = function () {
  var graph = this.editor.graph;

  if (graph.isEnabled()) {
    try {
      var cells = graph.getSelectionCells();
      var lookup = new mxDictionary();
      var newCells = [];

      for (var i = 0; i < cells.length; i++) {
        // Clones table rows instead of cells
        var cell = graph.isTableCell(cells[i]) ? graph.model.getParent(cells[i]) : cells[i];

        if (cell != null && !lookup.get(cell)) {
          lookup.put(cell, true);
          newCells.push(cell);
        }
      }

      graph.setSelectionCells(graph.duplicateCells(newCells, false));
    } catch (e) {
      this.handleError(e);
    }
  }
};

/**
 * Display a color dialog.
 */
EditorUi.prototype.pickColor = function (color, apply) {
  var graph = this.editor.graph;
  var selState = graph.cellEditor.saveSelection();
  var h =
    226 +
    (Math.ceil(ColorDialog.prototype.presetColors.length / 12) +
      Math.ceil(ColorDialog.prototype.defaultColors.length / 12)) *
      17;

  var dlg = new ColorDialog(
    this,
    color || 'none',
    function (color) {
      graph.cellEditor.restoreSelection(selState);
      apply(color);
    },
    function () {
      graph.cellEditor.restoreSelection(selState);
    }
  );
  this.showDialog(dlg.container, 230, h, true, false);
  dlg.init();
};

/**
 * Adds the label menu items to the given menu and parent.
 */
EditorUi.prototype.openFile = function () {
  // Closes dialog after open
  window.openFile = new OpenFile(
    mxUtils.bind(this, function (cancel) {
      this.hideDialog(cancel);
    })
  );

  // Removes openFile if dialog is closed
  this.showDialog(
    new OpenDialog(this).container,
    Editor.useLocalStorage ? 640 : 320,
    Editor.useLocalStorage ? 480 : 220,
    true,
    true,
    function () {
      window.openFile = null;
    }
  );
};

/**
 * Extracs the graph model from the given HTML data from a data transfer event.
 */
EditorUi.prototype.extractGraphModelFromHtml = function (data) {
  var result = null;

  try {
    var idx = data.indexOf('&lt;mxGraphModel ');

    if (idx >= 0) {
      var idx2 = data.lastIndexOf('&lt;/mxGraphModel&gt;');

      if (idx2 > idx) {
        result = data
          .substring(idx, idx2 + 21)
          .replace(/&gt;/g, '>')
          .replace(/&lt;/g, '<')
          .replace(/\\&quot;/g, '"')
          .replace(/\n/g, '');
      }
    }
  } catch (e) {
    // ignore
  }

  return result;
};

/**
 * Opens the given files in the editor.
 */
EditorUi.prototype.extractGraphModelFromEvent = function (evt) {
  var result = null;
  var data = null;

  if (evt != null) {
    var provider = evt.dataTransfer != null ? evt.dataTransfer : evt.clipboardData;

    if (provider != null) {
      if (document.documentMode == 10 || document.documentMode == 11) {
        data = provider.getData('Text');
      } else {
        data = mxUtils.indexOf(provider.types, 'text/html') >= 0 ? provider.getData('text/html') : null;

        if (mxUtils.indexOf(provider.types, 'text/plain' && (data == null || data.length == 0))) {
          data = provider.getData('text/plain');
        }
      }

      if (data != null) {
        data = Graph.zapGremlins(mxUtils.trim(data));

        // Tries parsing as HTML document with embedded XML
        var xml = this.extractGraphModelFromHtml(data);

        if (xml != null) {
          data = xml;
        }
      }
    }
  }

  if (data != null && this.isCompatibleString(data)) {
    result = data;
  }

  return result;
};

/**
 * Hook for subclassers to return true if event data is a supported format.
 * This implementation always returns false.
 */
EditorUi.prototype.isCompatibleString = function (data) {
  return false;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
EditorUi.prototype.saveFile = function (forceDialog) {
  if (!forceDialog && this.editor.filename != null) {
    this.save(this.editor.getOrCreateFilename());
  } else {
    var dlg = new FilenameDialog(
      this,
      this.editor.getOrCreateFilename(),
      mxResources.get('save'),
      mxUtils.bind(this, function (name) {
        this.save(name);
      }),
      null,
      mxUtils.bind(this, function (name) {
        if (name != null && name.length > 0) {
          return true;
        }

        mxUtils.confirm(mxResources.get('invalidName'));

        return false;
      })
    );
    this.showDialog(dlg.container, 300, 100, true, true);
    dlg.init();
  }
};

/**
 * Saves the current graph under the given filename.
 */
EditorUi.prototype.save = function (name) {
  if (name != null) {
    if (this.editor.graph.isEditing()) {
      this.editor.graph.stopEditing();
    }

    var xml = mxUtils.getXml(this.editor.getGraphXml());

    try {
      if (Editor.useLocalStorage) {
        if (localStorage.getItem(name) != null && !mxUtils.confirm(mxResources.get('replaceIt', [name]))) {
          return;
        }

        localStorage.setItem(name, xml);
        this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('saved')) + ' ' + new Date());
      } else {
        if (xml.length < MAX_REQUEST_SIZE) {
          new mxXmlRequest(
            SAVE_URL,
            'filename=' + encodeURIComponent(name) + '&xml=' + encodeURIComponent(xml)
          ).simulate(document, '_blank');
        } else {
          mxUtils.alert(mxResources.get('drawingTooLarge'));
          mxUtils.popup(xml);

          return;
        }
      }

      this.editor.setModified(false);
      this.editor.setFilename(name);
      this.updateDocumentTitle();
    } catch (e) {
      this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('errorSavingFile')));
    }
  }
};

/**
 * Executes the given layout.
 */
EditorUi.prototype.executeLayout = function (exec, animate, post) {
  var graph = this.editor.graph;

  if (graph.isEnabled()) {
    graph.getModel().beginUpdate();
    try {
      exec();
    } catch (e) {
      throw e;
    } finally {
      // Animates the changes in the graph model except
      // for Camino, where animation is too slow
      if (
        this.allowAnimation &&
        animate &&
        (navigator.userAgent == null || navigator.userAgent.indexOf('Camino') < 0)
      ) {
        // New API for animating graph layout results asynchronously
        var morph = new mxMorphing(graph);
        morph.addListener(
          mxEvent.DONE,
          mxUtils.bind(this, function () {
            graph.getModel().endUpdate();

            if (post != null) {
              post();
            }
          })
        );

        morph.startAnimation();
      } else {
        graph.getModel().endUpdate();

        if (post != null) {
          post();
        }
      }
    }
  }
};

/**
 * Hides the current menu.
 */
EditorUi.prototype.showImageDialog = function (title, value, fn, ignoreExisting) {
  var cellEditor = this.editor.graph.cellEditor;
  var selState = cellEditor.saveSelection();
  var newValue = mxUtils.prompt(title, value);
  cellEditor.restoreSelection(selState);

  if (newValue != null && newValue.length > 0) {
    var img = new Image();

    img.onload = function () {
      fn(newValue, img.width, img.height);
    };
    img.onerror = function () {
      fn(null);
      mxUtils.alert(mxResources.get('fileNotFound'));
    };

    img.src = newValue;
  } else {
    fn(null);
  }
};

/**
 * Hides the current menu.
 */
EditorUi.prototype.showLinkDialog = function (value, btnLabel, fn) {
  var dlg = new LinkDialog(this, value, btnLabel, fn);
  this.showDialog(dlg.container, 420, 90, true, true);
  dlg.init();
};

/**
 * Hides the current menu.
 */
EditorUi.prototype.showDataDialog = function (cell) {
  if (cell != null) {
    var dlg = new EditDataDialog(this, cell);
    this.showDialog(dlg.container, 480, 420, true, false, null, false);
    dlg.init();
  }
};

/**
 * Hides the current menu.
 */
EditorUi.prototype.showBackgroundImageDialog = function (apply, img) {
  apply =
    apply != null
      ? apply
      : mxUtils.bind(this, function (image) {
          var change = new ChangePageSetup(this, null, image);
          change.ignoreColor = true;

          this.editor.graph.model.execute(change);
        });

  var newValue = mxUtils.prompt(mxResources.get('backgroundImage'), img != null ? img.src : '');

  if (newValue != null && newValue.length > 0) {
    var img = new Image();

    img.onload = function () {
      apply(new mxImage(newValue, img.width, img.height), false);
    };
    img.onerror = function () {
      apply(null, true);
      mxUtils.alert(mxResources.get('fileNotFound'));
    };

    img.src = newValue;
  } else {
    apply(null);
  }
};

/**
 * Loads the stylesheet for this graph.
 */
EditorUi.prototype.setBackgroundImage = function (image) {
  this.editor.graph.setBackgroundImage(image);
  this.editor.graph.view.validateBackgroundImage();

  this.fireEvent(new mxEventObject('backgroundImageChanged'));
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
EditorUi.prototype.confirm = function (msg, okFn, cancelFn) {
  if (mxUtils.confirm(msg)) {
    if (okFn != null) {
      okFn();
    }
  } else if (cancelFn != null) {
    cancelFn();
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
EditorUi.prototype.createOutline = function (wnd) {
  var outline = new mxOutline(this.editor.graph);
  outline.border = 20;

  mxEvent.addListener(window, 'resize', function () {
    outline.update();
  });

  this.addListener('pageFormatChanged', function () {
    outline.update();
  });

  return outline;
};

// Alt+Shift+Keycode mapping to action
EditorUi.prototype.altShiftActions = {
  67: 'clearWaypoints', // Alt+Shift+C
  65: 'connectionArrows', // Alt+Shift+A
  76: 'editLink', // Alt+Shift+L
  80: 'connectionPoints', // Alt+Shift+P
  84: 'editTooltip', // Alt+Shift+T
  86: 'pasteSize', // Alt+Shift+V
  88: 'copySize', // Alt+Shift+X
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
EditorUi.prototype.createKeyHandler = function (editor) {
  var editorUi = this;
  var graph = this.editor.graph;
  var keyHandler = new mxKeyHandler(graph);

  var isEventIgnored = keyHandler.isEventIgnored;
  keyHandler.isEventIgnored = function (evt) {
    // Handles undo/redo/ctrl+./,/u via action and allows ctrl+b/i
    // only if editing value is HTML (except for FF and Safari)
    return (
      !(mxEvent.isShiftDown(evt) && evt.keyCode == 9) &&
      (!this.isControlDown(evt) ||
        mxEvent.isShiftDown(evt) ||
        (evt.keyCode != 90 && evt.keyCode != 89 && evt.keyCode != 188 && evt.keyCode != 190 && evt.keyCode != 85)) &&
      ((evt.keyCode != 66 && evt.keyCode != 73) ||
        !this.isControlDown(evt) ||
        (this.graph.cellEditor.isContentEditing() && !mxClient.IS_FF && !mxClient.IS_SF)) &&
      isEventIgnored.apply(this, arguments)
    );
  };

  // Ignores graph enabled state but not chromeless state
  keyHandler.isEnabledForEvent = function (evt) {
    return (
      !mxEvent.isConsumed(evt) &&
      this.isGraphEvent(evt) &&
      this.isEnabled() &&
      (editorUi.dialogs == null || editorUi.dialogs.length == 0)
    );
  };

  // Routes command-key to control-key on Mac
  keyHandler.isControlDown = function (evt) {
    return mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey);
  };

  var queue = [];
  var thread = null;

  // Helper function to move cells with the cursor keys
  function nudge(keyCode, stepSize, resize) {
    queue.push(function () {
      if (!graph.isSelectionEmpty() && graph.isEnabled()) {
        stepSize = stepSize != null ? stepSize : 1;

        if (resize) {
          // Resizes all selected vertices
          graph.getModel().beginUpdate();
          try {
            var cells = graph.getSelectionCells();

            for (var i = 0; i < cells.length; i++) {
              if (graph.getModel().isVertex(cells[i]) && graph.isCellResizable(cells[i])) {
                var geo = graph.getCellGeometry(cells[i]);

                if (geo != null) {
                  geo = geo.clone();

                  if (keyCode == 37) {
                    geo.width = Math.max(0, geo.width - stepSize);
                  } else if (keyCode == 38) {
                    geo.height = Math.max(0, geo.height - stepSize);
                  } else if (keyCode == 39) {
                    geo.width += stepSize;
                  } else if (keyCode == 40) {
                    geo.height += stepSize;
                  }

                  graph.getModel().setGeometry(cells[i], geo);
                }
              }
            }
          } finally {
            graph.getModel().endUpdate();
          }
        } else {
          // Moves vertices up/down in a stack layout
          var cell = graph.getSelectionCell();
          var parent = graph.model.getParent(cell);
          var layout = null;

          if (
            graph.getSelectionCount() == 1 &&
            graph.model.isVertex(cell) &&
            graph.layoutManager != null &&
            !graph.isCellLocked(cell)
          ) {
            layout = graph.layoutManager.getLayout(parent);
          }

          if (layout != null && layout.constructor == mxStackLayout) {
            var index = parent.getIndex(cell);

            if (keyCode == 37 || keyCode == 38) {
              graph.model.add(parent, cell, Math.max(0, index - 1));
            } else if (keyCode == 39 || keyCode == 40) {
              graph.model.add(parent, cell, Math.min(graph.model.getChildCount(parent), index + 1));
            }
          } else {
            var cells = graph.getMovableCells(graph.getSelectionCells());
            var realCells = [];

            for (var i = 0; i < cells.length; i++) {
              // TODO: Use getCompositeParent
              var style = graph.getCurrentCellStyle(cells[i]);

              if (mxUtils.getValue(style, 'part', '0') == '1') {
                var parent = graph.model.getParent(cells[i]);

                if (graph.model.isVertex(parent) && mxUtils.indexOf(cells, parent) < 0) {
                  realCells.push(parent);
                }
              } else {
                realCells.push(cells[i]);
              }
            }

            if (realCells.length > 0) {
              cells = realCells;
              var dx = 0;
              var dy = 0;

              if (keyCode == 37) {
                dx = -stepSize;
              } else if (keyCode == 38) {
                dy = -stepSize;
              } else if (keyCode == 39) {
                dx = stepSize;
              } else if (keyCode == 40) {
                dy = stepSize;
              }

              graph.moveCells(cells, dx, dy);
            }
          }
        }
      }
    });

    if (thread != null) {
      window.clearTimeout(thread);
    }

    thread = window.setTimeout(function () {
      if (queue.length > 0) {
        graph.getModel().beginUpdate();

        try {
          for (var i = 0; i < queue.length; i++) {
            queue[i]();
          }

          queue = [];
        } finally {
          graph.getModel().endUpdate();
        }
      }
    }, 200);
  }

  // Overridden to handle special alt+shift+cursor keyboard shortcuts
  var directions = {
    37: mxConstants.DIRECTION_WEST,
    38: mxConstants.DIRECTION_NORTH,
    39: mxConstants.DIRECTION_EAST,
    40: mxConstants.DIRECTION_SOUTH,
  };

  var keyHandlerGetFunction = keyHandler.getFunction;

  mxKeyHandler.prototype.getFunction = function (evt) {
    if (graph.isEnabled()) {
      // TODO: Add alt modified state in core API, here are some specific cases
      if (mxEvent.isShiftDown(evt) && mxEvent.isAltDown(evt)) {
        var action = editorUi.actions.get(editorUi.altShiftActions[evt.keyCode]);

        if (action != null) {
          return action.funct;
        }
      }

      if (evt.keyCode == 9 && mxEvent.isAltDown(evt)) {
        if (graph.cellEditor.isContentEditing()) {
          // Alt+Shift+Tab while editing
          return function () {
            document.execCommand('outdent', false, null);
          };
        } else if (mxEvent.isShiftDown(evt)) {
          // Alt+Shift+Tab
          return function () {
            graph.selectParentCell();
          };
        } else {
          // Alt+Tab
          return function () {
            graph.selectChildCell();
          };
        }
      } else if (directions[evt.keyCode] != null && !graph.isSelectionEmpty()) {
        // On macOS, Control+Cursor is used by Expose so allow for Alt+Control to resize
        if (!this.isControlDown(evt) && mxEvent.isShiftDown(evt) && mxEvent.isAltDown(evt)) {
          if (graph.model.isVertex(graph.getSelectionCell())) {
            return function () {
              var cells = graph.connectVertex(
                graph.getSelectionCell(),
                directions[evt.keyCode],
                graph.defaultEdgeLength,
                evt,
                true
              );

              if (cells != null && cells.length > 0) {
                if (cells.length == 1 && graph.model.isEdge(cells[0])) {
                  graph.setSelectionCell(graph.model.getTerminal(cells[0], false));
                } else {
                  graph.setSelectionCell(cells[cells.length - 1]);
                }

                graph.scrollCellToVisible(graph.getSelectionCell());

                if (editorUi.hoverIcons != null) {
                  editorUi.hoverIcons.update(graph.view.getState(graph.getSelectionCell()));
                }
              }
            };
          }
        } else {
          // Avoids consuming event if no vertex is selected by returning null below
          // Cursor keys move and resize (ctrl) cells
          if (this.isControlDown(evt)) {
            return function () {
              nudge(evt.keyCode, mxEvent.isShiftDown(evt) ? graph.gridSize : null, true);
            };
          } else {
            return function () {
              nudge(evt.keyCode, mxEvent.isShiftDown(evt) ? graph.gridSize : null);
            };
          }
        }
      }
    }

    return keyHandlerGetFunction.apply(this, arguments);
  };

  // Binds keystrokes to actions
  keyHandler.bindAction = mxUtils.bind(this, function (code, control, key, shift) {
    var action = this.actions.get(key);

    if (action != null) {
      var f = function () {
        if (action.isEnabled()) {
          action.funct();
        }
      };

      if (control) {
        if (shift) {
          keyHandler.bindControlShiftKey(code, f);
        } else {
          keyHandler.bindControlKey(code, f);
        }
      } else {
        if (shift) {
          keyHandler.bindShiftKey(code, f);
        } else {
          keyHandler.bindKey(code, f);
        }
      }
    }
  });

  var ui = this;
  var keyHandlerEscape = keyHandler.escape;
  keyHandler.escape = function (evt) {
    keyHandlerEscape.apply(this, arguments);
  };

  // Ignores enter keystroke. Remove this line if you want the
  // enter keystroke to stop editing. N, W, T are reserved.
  keyHandler.enter = function () {};

  keyHandler.bindControlShiftKey(36, function () {
    graph.exitGroup();
  }); // Ctrl+Shift+Home
  keyHandler.bindControlShiftKey(35, function () {
    graph.enterGroup();
  }); // Ctrl+Shift+End
  keyHandler.bindShiftKey(36, function () {
    graph.home();
  }); // Ctrl+Shift+Home
  keyHandler.bindKey(35, function () {
    graph.refresh();
  }); // End
  keyHandler.bindAction(107, true, 'zoomIn'); // Ctrl+Plus
  keyHandler.bindAction(109, true, 'zoomOut'); // Ctrl+Minus
  keyHandler.bindAction(80, true, 'print'); // Ctrl+P
  keyHandler.bindAction(79, true, 'outline', true); // Ctrl+Shift+O

  if (!this.editor.chromeless || this.editor.editable) {
    keyHandler.bindControlKey(36, function () {
      if (graph.isEnabled()) {
        graph.foldCells(true);
      }
    }); // Ctrl+Home
    keyHandler.bindControlKey(35, function () {
      if (graph.isEnabled()) {
        graph.foldCells(false);
      }
    }); // Ctrl+End
    keyHandler.bindControlKey(13, function () {
      ui.ctrlEnter();
    }); // Ctrl+Enter
    keyHandler.bindAction(8, false, 'delete'); // Backspace
    keyHandler.bindAction(8, true, 'deleteAll'); // Ctrl+Backspace
    keyHandler.bindAction(8, false, 'deleteLabels', true); // Shift+Backspace
    keyHandler.bindAction(46, false, 'delete'); // Delete
    keyHandler.bindAction(46, true, 'deleteAll'); // Ctrl+Delete
    keyHandler.bindAction(46, false, 'deleteLabels', true); // Shift+Delete
    keyHandler.bindAction(36, false, 'resetView'); // Home
    keyHandler.bindAction(72, true, 'fitWindow', true); // Ctrl+Shift+H
    keyHandler.bindAction(74, true, 'fitPage'); // Ctrl+J
    keyHandler.bindAction(74, true, 'fitTwoPages', true); // Ctrl+Shift+J
    keyHandler.bindAction(48, true, 'customZoom'); // Ctrl+0
    keyHandler.bindAction(82, true, 'turn'); // Ctrl+R
    keyHandler.bindAction(82, true, 'clearDefaultStyle', true); // Ctrl+Shift+R
    keyHandler.bindAction(83, true, 'save'); // Ctrl+S
    keyHandler.bindAction(83, true, 'saveAs', true); // Ctrl+Shift+S
    keyHandler.bindAction(65, true, 'selectAll'); // Ctrl+A
    keyHandler.bindAction(65, true, 'selectNone', true); // Ctrl+A
    keyHandler.bindAction(73, true, 'selectVertices', true); // Ctrl+Shift+I
    keyHandler.bindAction(69, true, 'selectEdges', true); // Ctrl+Shift+E
    keyHandler.bindAction(69, true, 'editStyle'); // Ctrl+E
    keyHandler.bindAction(66, true, 'bold'); // Ctrl+B
    keyHandler.bindAction(66, true, 'toBack', true); // Ctrl+Shift+B
    keyHandler.bindAction(70, true, 'toFront', true); // Ctrl+Shift+F
    keyHandler.bindAction(68, true, 'duplicate'); // Ctrl+D
    keyHandler.bindAction(68, true, 'setAsDefaultStyle', true); // Ctrl+Shift+D
    keyHandler.bindAction(90, true, 'undo'); // Ctrl+Z
    keyHandler.bindAction(89, true, 'autosize', true); // Ctrl+Shift+Y
    keyHandler.bindAction(88, true, 'cut'); // Ctrl+X
    keyHandler.bindAction(67, true, 'copy'); // Ctrl+C
    keyHandler.bindAction(86, true, 'paste'); // Ctrl+V
    keyHandler.bindAction(71, true, 'group'); // Ctrl+G
    keyHandler.bindAction(77, true, 'editData'); // Ctrl+M
    keyHandler.bindAction(71, true, 'grid', true); // Ctrl+Shift+G
    keyHandler.bindAction(73, true, 'italic'); // Ctrl+I
    keyHandler.bindAction(76, true, 'lockUnlock'); // Ctrl+L
    keyHandler.bindAction(76, true, 'layers', true); // Ctrl+Shift+L
    keyHandler.bindAction(80, true, 'formatPanel', true); // Ctrl+Shift+P
    keyHandler.bindAction(85, true, 'underline'); // Ctrl+U
    keyHandler.bindAction(85, true, 'ungroup', true); // Ctrl+Shift+U
    keyHandler.bindAction(190, true, 'superscript'); // Ctrl+.
    keyHandler.bindAction(188, true, 'subscript'); // Ctrl+,
    keyHandler.bindAction(9, false, 'indent', true); // Shift+Tab,
    keyHandler.bindKey(13, function () {
      if (graph.isEnabled()) {
        graph.startEditingAtCell();
      }
    }); // Enter
    keyHandler.bindKey(113, function () {
      if (graph.isEnabled()) {
        graph.startEditingAtCell();
      }
    }); // F2
  }

  if (!mxClient.IS_WIN) {
    keyHandler.bindAction(90, true, 'redo', true); // Ctrl+Shift+Z
  } else {
    keyHandler.bindAction(89, true, 'redo'); // Ctrl+Y
  }

  return keyHandler;
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
EditorUi.prototype.destroy = function () {
  if (this.editor != null) {
    this.editor.destroy();
    this.editor = null;
  }

  if (this.menubar != null) {
    this.menubar.destroy();
    this.menubar = null;
  }

  if (this.toolbar != null) {
    this.toolbar.destroy();
    this.toolbar = null;
  }

  if (this.sidebar != null) {
    this.sidebar.destroy();
    this.sidebar = null;
  }

  if (this.keyHandler != null) {
    this.keyHandler.destroy();
    this.keyHandler = null;
  }

  if (this.keydownHandler != null) {
    mxEvent.removeListener(document, 'keydown', this.keydownHandler);
    this.keydownHandler = null;
  }

  if (this.keyupHandler != null) {
    mxEvent.removeListener(document, 'keyup', this.keyupHandler);
    this.keyupHandler = null;
  }

  if (this.resizeHandler != null) {
    mxEvent.removeListener(window, 'resize', this.resizeHandler);
    this.resizeHandler = null;
  }

  if (this.gestureHandler != null) {
    mxEvent.removeGestureListeners(document, this.gestureHandler);
    this.gestureHandler = null;
  }

  if (this.orientationChangeHandler != null) {
    mxEvent.removeListener(window, 'orientationchange', this.orientationChangeHandler);
    this.orientationChangeHandler = null;
  }

  if (this.scrollHandler != null) {
    mxEvent.removeListener(window, 'scroll', this.scrollHandler);
    this.scrollHandler = null;
  }

  if (this.destroyFunctions != null) {
    for (var i = 0; i < this.destroyFunctions.length; i++) {
      this.destroyFunctions[i]();
    }

    this.destroyFunctions = null;
  }

  var c = [
    this.menubarContainer,
    this.toolbarContainer,
    this.sidebarContainer,
    this.formatContainer,
    this.diagramContainer,
    // this.footerContainer,
    this.chromelessToolbar,
    this.hsplit,
    this.sidebarFooterContainer,
    this.layersDialog,
  ];

  for (var i = 0; i < c.length; i++) {
    if (c[i] != null && c[i].parentNode != null) {
      c[i].parentNode.removeChild(c[i]);
    }
  }
};
/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
/**
 * Editor constructor executed on page load.
 */
Editor = function (chromeless, themes, model, graph, editable) {
  mxEventSource.call(this);
  this.chromeless = chromeless != null ? chromeless : this.chromeless;
  this.initStencilRegistry();
  this.graph = graph || this.createGraph(themes, model);
  this.editable = editable != null ? editable : !chromeless;
  this.undoManager = this.createUndoManager();
  this.status = '';

  this.getOrCreateFilename = function () {
    return this.filename || mxResources.get('drawing', [Editor.pageCounter]) + '.xml';
  };

  this.getFilename = function () {
    return this.filename;
  };

  // Sets the status and fires a statusChanged event
  this.setStatus = function (value) {
    this.status = value;
    this.fireEvent(new mxEventObject('statusChanged'));
  };

  // Returns the current status
  this.getStatus = function () {
    return this.status;
  };

  // Updates modified state if graph changes
  this.graphChangeListener = function (sender, eventObject) {
    var edit = eventObject != null ? eventObject.getProperty('edit') : null;

    if (edit == null || !edit.ignoreEdit) {
      this.setModified(true);
    }
  };

  this.graph.getModel().addListener(
    mxEvent.CHANGE,
    mxUtils.bind(this, function () {
      this.graphChangeListener.apply(this, arguments);
    })
  );

  // Sets persistent graph state defaults
  this.graph.resetViewOnRootChange = false;
  this.init();
};

/**
 * Counts open editor tabs (must be global for cross-window access)
 */
Editor.pageCounter = 0;

// Cross-domain window access is not allowed in FF, so if we
// were opened from another domain then this will fail.
(function () {
  try {
    var op = window;

    while (
      op.opener != null &&
      typeof op.opener.Editor !== 'undefined' &&
      !isNaN(op.opener.Editor.pageCounter) &&
      // Workaround for possible infinite loop in FF https://drawio.atlassian.net/browse/DS-795
      op.opener != op
    ) {
      op = op.opener;
    }

    // Increments the counter in the first opener in the chain
    if (op != null) {
      op.Editor.pageCounter++;
      Editor.pageCounter = op.Editor.pageCounter;
    }
  } catch (e) {
    // ignore
  }
})();

/**
 * Specifies if local storage should be used (eg. on the iPad which has no filesystem)
 */
Editor.useLocalStorage = typeof Storage != 'undefined' && mxClient.IS_IOS;

/**
 *
 */
Editor.moveImage = mxClient.IS_SVG
  ? 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI4cHgiIGhlaWdodD0iMjhweCI+PGc+PC9nPjxnPjxnPjxnPjxwYXRoIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIuNCwyLjQpc2NhbGUoMC44KXJvdGF0ZSg0NSwxMiwxMikiIHN0cm9rZT0iIzI5YjZmMiIgZmlsbD0iIzI5YjZmMiIgZD0iTTE1LDNsMi4zLDIuM2wtMi44OSwyLjg3bDEuNDIsMS40MkwxOC43LDYuN0wyMSw5VjNIMTV6IE0zLDlsMi4zLTIuM2wyLjg3LDIuODlsMS40Mi0xLjQyTDYuNyw1LjNMOSwzSDNWOXogTTksMjEgbC0yLjMtMi4zbDIuODktMi44N2wtMS40Mi0xLjQyTDUuMywxNy4zTDMsMTV2Nkg5eiBNMjEsMTVsLTIuMywyLjNsLTIuODctMi44OWwtMS40MiwxLjQybDIuODksMi44N0wxNSwyMWg2VjE1eiIvPjwvZz48L2c+PC9nPjwvc3ZnPgo='
  : IMAGE_PATH + '/move.png';

/**
 *
 */
Editor.rowMoveImage = mxClient.IS_SVG
  ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAEBAMAAACw6DhOAAAAGFBMVEUzMzP///9tbW1QUFCKiopBQUF8fHxfX1/IXlmXAAAAFElEQVQImWNgNVdzYBAUFBRggLMAEzYBy29kEPgAAAAASUVORK5CYII='
  : IMAGE_PATH + '/thumb_horz.png';

/**
 * Images below are for lightbox and embedding toolbars.
 */
Editor.helpImage = mxClient.IS_SVG
  ? 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJub25lIiBkPSJNMCAwaDI0djI0SDB6Ii8+PHBhdGggZD0iTTExIDE4aDJ2LTJoLTJ2MnptMS0xNkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6bTAtMTRjLTIuMjEgMC00IDEuNzktNCA0aDJjMC0xLjEuOS0yIDItMnMyIC45IDIgMmMwIDItMyAxLjc1LTMgNWgyYzAtMi4yNSAzLTIuNSAzLTUgMC0yLjIxLTEuNzktNC00LTR6Ii8+PC9zdmc+'
  : IMAGE_PATH + '/help.png';

/**
 * Sets the default font size.
 */
Editor.checkmarkImage = mxClient.IS_SVG
  ? 'data:image/gif;base64,R0lGODlhFQAVAMQfAGxsbHx8fIqKioaGhvb29nJycvr6+sDAwJqamltbW5OTk+np6YGBgeTk5Ly8vJiYmP39/fLy8qWlpa6ursjIyOLi4vj4+N/f3+3t7fT09LCwsHZ2dubm5r6+vmZmZv///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OEY4NTZERTQ5QUFBMTFFMUE5MTVDOTM5MUZGMTE3M0QiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OEY4NTZERTU5QUFBMTFFMUE5MTVDOTM5MUZGMTE3M0QiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Rjg1NkRFMjlBQUExMUUxQTkxNUM5MzkxRkYxMTczRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Rjg1NkRFMzlBQUExMUUxQTkxNUM5MzkxRkYxMTczRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAB8ALAAAAAAVABUAAAVI4CeOZGmeaKqubKtylktSgCOLRyLd3+QJEJnh4VHcMoOfYQXQLBcBD4PA6ngGlIInEHEhPOANRkaIFhq8SuHCE1Hb8Lh8LgsBADs='
  : IMAGE_PATH + '/checkmark.gif';

/**
 * Images below are for lightbox and embedding toolbars.
 */
Editor.maximizeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVBAMAAABbObilAAAAElBMVEUAAAAAAAAAAAAAAAAAAAAAAADgKxmiAAAABXRSTlMA758vX1Pw3BoAAABJSURBVAjXY8AJQkODGBhUQ0MhbAUGBiYY24CBgRnGFmZgMISwgwwDGRhEhVVBbAVmEQYGRwMmBjIAQi/CTIRd6G5AuA3dzYQBAHj0EFdHkvV4AAAAAElFTkSuQmCC';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.zoomOutImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVBAMAAABbObilAAAAElBMVEUAAAAAAAAsLCxxcXEhISFgYGChjTUxAAAAAXRSTlMAQObYZgAAAEdJREFUCNdjIAMwCQrB2YKCggJQJqMwA7MglK1owMBgqABVApITgLJZXFxgbIQ4Qj3CHIT5ggoIe5kgNkM1KSDYKBKqxPkDAPo5BAZBE54hAAAAAElFTkSuQmCC';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.zoomInImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVBAMAAABbObilAAAAElBMVEUAAAAAAAAsLCwhISFxcXFgYGBavKaoAAAAAXRSTlMAQObYZgAAAElJREFUCNdjIAMwCQrB2YKCggJQJqMIA4sglK3owMzgqABVwsDMwCgAZTMbG8PYCHGEeoQ5CPMFFRD2MkFshmpSQLBRJFSJ8wcAEqcEM2uhl2MAAAAASUVORK5CYII=';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.zoomFitImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVBAMAAABbObilAAAAD1BMVEUAAAAAAAAwMDBwcHBgYGC1xl09AAAAAXRSTlMAQObYZgAAAEFJREFUCNdjIAMwCQrB2YKCggJQJqMwA7MglK1owMBgqABVApITwMdGqEeYgzBfUAFhLxPEZqgmBQQbRUKFOH8AAK5OA3lA+FFOAAAAAElFTkSuQmCC';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.layersImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAMAAACeyVWkAAAAaVBMVEUAAAAgICAICAgdHR0PDw8WFhYICAgLCwsXFxcvLy8ODg4uLi4iIiIqKiokJCQYGBgKCgonJycFBQUCAgIqKiocHBwcHBwODg4eHh4cHBwnJycJCQkUFBQqKiojIyMuLi4ZGRkgICAEBATOWYXAAAAAGnRSTlMAD7+fnz8/H7/ff18/77+vr5+fn39/b28fH2xSoKsAAACQSURBVBjTrYxJEsMgDARZZMAY73sgCcn/HxnhKtnk7j6oRq0psfuoyndZ/SuODkHPLzfVT6KeyPePnJ7KrnkRjWMXTn4SMnN8mXe2SSM3ts8L/ZUxxrbAULSYJJULE0Iw9pjpenoICcgcX61mGgTgtCv9Be99pzCoDhNQWQnchD1mup5++CYGcoQexajZbfwAj/0MD8ZOaUgAAAAASUVORK5CYII=';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.previousImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAAh0lEQVQ4je3UsQnCUBCA4U8hpa1NsoEjpHQJS0dxADdwEMuMIJkgA1hYChbGQgMi+JC8q4L/AB/vDu7x74cWWEZhJU44RmA1zujR5GIbXF9YNrjD/Q0bDRY4fEBZ4P4LlgTnCbAf84pUM8/9hY08tMUtEoQ1LpEgrNBFglChFXR6Q6GfwwR6AGKJMF74Vtt3AAAAAElFTkSuQmCC';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.nextImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAAi0lEQVQ4jeXUIQ7CUAwA0MeGxWI2yylwnALJUdBcgYvM7QYLmjOQIAkIPmJZghiIvypoUtX0tfnJL38X5ZfaEgUeUcManFBHgS0SLlhHggk3bCPBhCf2keCQR8wjwYTDp6YiZxJmOU1jGw7vGALescuBxsArNlOwd/CM1VSM/ut1qCIw+uOwiMJ+OF4CQzBCXm3hyAAAAABJRU5ErkJggg==';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.editImage = mxClient.IS_SVG
  ? 'data:image/gif;base64,R0lGODlhCwALAIABAFdXV////yH5BAEAAAEALAAAAAALAAsAAAIZjB8AiKuc4jvLOGqzrjX6zmkWyChXaUJBAQA7'
  : IMAGE_PATH + '/edit.gif';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.zoomOutLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAilBMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////2N2iNAAAALXRSTlMA+vTcKMM96GRBHwXxi0YaX1HLrKWhiHpWEOnOr52Vb2xKSDcT19PKv5l/Ngdk8+viAAABJklEQVQ4y4WT2XaDMAxEvWD2nSSUNEnTJN3r//+9Sj7ILAY6L0ijC4ONYVZRpo6cByrz2YKSUGorGTpz71lPVHvT+avoB5wIkU/mxk8veceSuNoLg44IzziXjvpih72wKQnm8yc2UoiP/LAd8jQfe2Xf4Pq+2EyYIvv9wbzHHCgwxDdlBtWZOdqDfTCVgqpygQpsZaojVAVc9UjQxnAJDIBhiQv84tq3gMQCAVTxVoSibXJf8tMuc7e1TB/DCmejBNg/w1Y3c+AM5vv4w7xM59/oXamrHaLVqPQ+OTCnmMZxgz0SdL5zji0/ld6j88qGa5KIiBB6WeJGKfUKwSMKLuXgvl1TW0tm5R9UQL/efSDYsnzxD8CinhBsTTdugJatKpJwf8v+ADb8QmvW7AeAAAAAAElFTkSuQmCC';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.zoomInLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAilBMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////2N2iNAAAALXRSTlMA+vTcKMM96GRBHwXxi0YaX1HLrKWhiHpWEOnOr52Vb2xKSDcT19PKv5l/Ngdk8+viAAABKElEQVQ4y4WT6WKCMBCENwkBwn2oFKvWqr3L+79es4EkQIDOH2d3Pxk2ABiJlB8JCXjqw4LikHVGLHTm3nM3UeVN5690GBBN0GwyV/3kkrUQR+WeKnREeKpzaXWd77CmJiXGfPIEI4V4yQ9TIW/ntlcMBe731Vts9w5TWG8F5j3mQI4hvrKpdGeYA7CX9qAcl650gVJartxRuhyHVghF8idQAIbFLvCLu28BsQEC6aKtCK6Pyb3JT7PmbmtNH8Ny56CotD/2qOs5cJbuffxgXmCib+xddVU5RNOhkvvkhTlFehzVWCOh3++MYElOhfdovaImnRYVmqDdsuhNp1QrBBE6uGC2+3ZNjGdg5B94oD+9uyVgWT79BwAxEBTWdOu3bWBVgsn/N/AHUD9IC01Oe40AAAAASUVORK5CYII=';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.actualSizeLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAilBMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////2N2iNAAAALXRSTlMA+vTcKMM96GRBHwXxi0YaX1HLrKWhiHpWEOnOr52Vb2xKSDcT19PKv5l/Ngdk8+viAAABIUlEQVQ4y4WT2XqDIBCFBxDc9yTWNEnTJN3r+79eGT4BEbXnaubMr8dBBaM450dCQp4LWFAascGIRd48eB4cNYE7f6XjgGiCFs5c+dml6CFN6j1V6IQIlHPpdV/usKcmJcV88gQTRXjLD9Mhb+fWq8YG9/uCmTCFjeeDeY85UGKIUGUuqzN42kv7oCouq9oHamlzVR1lVfpAIu1QVRiW+sAv7r4FpAYIZZVsRXB9TP5Dfpo1d1trCgzz1iiptH/sUbdz4CzN9+mLeXHn3+hdddd4RDegsrvzwZwSs2GLPRJidAqCLTlVwaMPqpYMWjTWBB2WRW86pVkhSKyDK2bdt2tmagZG4sBD/evdLQHLEvQfAOKRoLCmG1FAB6uKmby+gz+REDn7O5+EwQAAAABJRU5ErkJggg==';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.printLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAXVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9RKvvlAAAAHnRSTlMAydnl77qbMLT093H7K4Nd4Ktn082+lYt5bkklEgP44nQSAAAApUlEQVQ4y73P2Q6DIBRF0cOgbRHHzhP//5m9mBAQKjG1cT0Yc7ITAMu1LNQgUZiQ2DYoNQ0sCQb6qgHAfRx48opq3J9AZ6xuF7uOew8Ik1OsCZRS2UAC9V+D9a+QZYxNA45YFQftPtSkATOhw7dAc0vPBwKWiIOjP0JZ0yMuQJ27g36DipOUsqRAM0dR8KD1/ILHaHSE/w8DIx09E3g/BTce6rHUB5sAPKvfF+JdAAAAAElFTkSuQmCC';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.layersLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAmVBMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+/v7///+bnZkkAAAAMnRSTlMABPr8ByiD88KsTi/rvJb272mjeUA1CuPe1M/KjVxYHxMP6KZ0S9nYzGRGGRaznpGIbzaGUf0AAAHESURBVDjLbZLZYoIwEEVDgLCjbKIgAlqXqt3m/z+uNwu1rcyDhjl3ktnYL7OY254C0VX3yWFZfzDrOClbbgKxi0YDHjwl4jbnRkXxJS/C1YP3DbBhD1n7Ex4uaAqdVDb3yJ/4J/3nJD2to/ngQz/DfUvzMp4JJ5sSCaF5oXmemgQDfDxzbi+Kq4sU+vNcuAmx94JtyOP2DD4Epz2asWSCz4Z/4fECxyNj9zC9xNLHcdPEO+awDKeSaUu0W4twZQiO2hYVisTR3RCtK/c1X6t4xMEpiGqXqVntEBLolkZZsKY4QtwH6jzq67dEHlJysB1aNOD3XT7n1UkasQN59L4yC2RELMDSeCRtz3yV22Ub3ozIUTknYx8JWqDdQxbUes98cR2kZtUSveF/bAhcedwEWmlxIkpZUy4XOCb6VBjjxHvbwo/1lBAHHi2JCr0NI570QhyHq/DhJoE2lLgyA4RVe6KmZ47O/3b86MCP0HWa73A8/C3SUc5Qc1ajt6fgpXJ+RGpMvDSchepZDOOQRcZVIKcK90x2D7etqtI+56+u6n3sPriO6nfphitR4+O2m3EbM7lh3me1FM1o+LMI887rN+s3/wZdTFlpNVJiOAAAAABJRU5ErkJggg==';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.closeLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAUVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////8IN+deAAAAGnRSTlMAuvAIg/dDM/QlOeuFhj0S5s4vKgzjxJRQNiLSey0AAADNSURBVDjLfZLbEoMgDEQjRRRs1XqX///QNmOHJSnjPkHOGR7IEmeoGtJZstnwjqbRfIsmgEdtPCqe9Ynz7ZSc07rE2QiSc+qv8TvjRXA2PDUm3dpe82iJhOEUfxJJo3aCv+jKmRmH4lcCjCjeh9GWOdL/GZZkXH3PYYDrHBnfc4D/RVZf5sjoC1was+Y6HQxwaUxFvq/a0Pv343VCTxfBSRiB+ab3M3eiQZXmMNBJ3Y8pGRZtYQ7DgHMXJEdPLTaN/qBjzJOBc3nmNcbsA16bMR0oLqf+AAAAAElFTkSuQmCC';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.editLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAgVBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9d3yJTAAAAKnRSTlMA+hzi3nRQWyXzkm0h2j3u54gzEgSXjlYoTBgJxL2loGpAOS3Jt7Wxm35Ga7gRAAAA6UlEQVQ4y63Q2XaCMBSF4Q0JBasoQ5DJqbXjfv8HbCK2BZNwo/8FXHx7rcMC7lQu0iX8qU/qtvAWCpoqH8dYzS0SwaV5eK/UAf8X9pd2CWKzuF5Jrftp1owXwnIGLUaL3PYndOHf4kNNXWrXK/m7CHunk7K8LE6YtBpcknwG9GKxnroY+ylBXcx4xKyx/u/EuXi509cP9V7OO1oyHnzrdFTcqLG/4ibBA5pIMr/4xvKzuQDkVy9wW8SgBFD6HDvuzMvrZcC9QlkfMzI7w64m+b4PqBMNHB05lH21PVxJo2/fBXxV4hB38PcD+5AkI4FuETsAAAAASUVORK5CYII=';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.previousLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAPFBMVEUAAAD////////////////////////////////////////////////////////////////////////////YSWgTAAAAE3RSTlMA7fci493c0MW8uJ6CZks4MxQHEZL6ewAAAFZJREFUOMvdkskRgDAMA4lDwg2B7b9XOlge/KKvdsa25KFb5XlRvxXC/DNBEv8IFNjBgGdDgXtFgTyhwDXiQAUHCvwa4Uv6mR6UR+1led2mVonvl+tML45qCQNQLIx7AAAAAElFTkSuQmCC';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.nextLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAPFBMVEUAAAD////////////////////////////////////////////////////////////////////////////YSWgTAAAAE3RSTlMA7fci493c0MW8uJ6CZks4MxQHEZL6ewAAAFRJREFUOMvd0skRgCAQBVEFwQ0V7fxzNQP6wI05v6pZ/kyj1b7FNgik2gQzzLcAwiUAigHOTwDHK4A1CmB5BJANJG1hQ9qafYcqFlZP3IFc9eVGrR+iIgkDQRUXIAAAAABJRU5ErkJggg==';

/**
 * Specifies the image to be used for the refresh button.
 */
Editor.refreshLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAolBMVEUAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8ELnaCAAAANXRSTlMABfyE2QKU+dfNyyDyoVYKwnTv7N+6rntsYlFNQjEqEw316uSzf2c1JB3GvqebiVw6GAjQB4DQr10AAAE7SURBVDjLvZLXcoMwEABPIgRCx3TT3A3udqL//7UgAdGRcR4yk8k+idsdmgS/QyWEqD/axS2JDV33zlnzLHIzQ2MDq9OeJ3m8l76KKENYlxrmM/b65Ys1+8YxnTEZFIEY0vVhszFWfUGZDJpQTDznTgAe5k4XhQxILB7ruzBQn+kkyDXuHfRtjoYDEvH7J9Lz98dBZXXL94X0Ofco2PFlChKbjVzEdakoSlKjoNoqPYkJ/wUZAYwc+PpLj1Ei7+jdoBWlwQZoJv2H1w3CWgRvo7dd9DP5btgwCWz0M02+oVoxCcIWeY9PNmR6B++m9prMxYEISpCBYBlfy9bc745is7UUULAem1Ww7FfalsiA2uaJsgmWP3pQI9q9/yMLkaaHAp2fxhHff/cNq7dBdHXhGW7l+Mo2zU0Cf8knJ2xA0oJ8enwAAAAASUVORK5CYII=';

/**
 * Specifies the image to be used for the back button.
 */
Editor.backLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAclBMVEUAAAD////////////////+/v7////////////////////////////////////////////+/v7///////////////////////////////////////////////////////////////////////////////8vKLfTAAAAJXRSTlMACh7h9gby3NLIwzwZ55uVJgH57b+8tbCljYV1RRMQ46FrTzQw+vtxOQAAAJ5JREFUOMuF00cWgzAQA1DRDQFCbwFSdf8rZpdVrNH2z3tuMv7mldZQ2WN2yi8x+TT8JvyTkqvwpiKvwsOIrA1fWr+XGTklfj8dOQR+D3KyUF6QufBkJN0hfCazEv6sZBRCJDUcPasGKpu1RLtYE8lkHAPBQLoTsK/SfAyRw5FjAuhCzC2MSj0gJ+66lHatgXdKboD9tfREB5m9/+3iC9jHDYvsGNcUAAAAAElFTkSuQmCC';

/**
 * Specifies the image to be used for the back button.
 */
Editor.fullscreenLargeImage =
  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAllBMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AJcWoAAAAMXRSTlMA+wIFxPWPCIb446tnUxmsoIykgxTe29jQnpKBe2MNsZhVTR/KyLuWbFhEPjUq7L9z+bQj+gAAAWxJREFUOMttk4l2gkAMRTODCO4FtQgIbnWpS9v8/881iZFh8R51NO8GJ+gAjMN8zuTRFSw04cIOHQcqFHH6oaQFGxf0jeBjEgB8Y52TpW9Ag4zB5QICWOtHrgwGuFZBcw+gPP0MFS7+iiD5inOmDIQS9sZgTwUzwEzyxhxHVEEU7NdDUXsqUPtqjIgR2IZSCT4upzSeIeOdcMHnfDsx3giPoezfU6MrQGB5//SckLEG2xYscK4GfnUFqaix39zrwooaOD/cXoYuvHKQIc7pzd3HVPusp6t2FAW/RmjMonbl8vwHDeZo/GkleJC7e+p5XA/rAq1X/V10wKag04rBpa2/d0LL4OYYceOEtsG5jyMntI1wS+N1BGcQBl/CoLoPOl9ABrW/BP53e1bwSJHHlkIVchJwmHwyyfJ4kIvEnKtwkxNSEct83KSChT7WiWgDZ3ccZ0BM4tloJow2YUAtifNT3njnyD+y/pMsnP4DN3Y4yl1Gyk0AAAAASUVORK5CYII=';

/**
 * All fill styles supported by rough.js.
 */
Editor.roughFillStyles = [
  { val: 'auto', dispName: 'Auto' },
  { val: 'hachure', dispName: 'Hachure' },
  { val: 'solid', dispName: 'Solid' },
  { val: 'zigzag', dispName: 'ZigZag' },
  { val: 'cross-hatch', dispName: 'Cross Hatch' },
  { val: 'dots', dispName: 'Dots' },
  { val: 'dashed', dispName: 'Dashed' },
  { val: 'zigzag-line', dispName: 'ZigZag Line' },
];

/**
 * Graph themes for the format panel.
 */
Editor.themes = null;

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.ctrlKey = mxClient.IS_MAC ? 'Cmd' : 'Ctrl';

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.hintOffset = 20;

/**
 * Specifies if the diagram should be saved automatically if possible. Default
 * is true.
 */
Editor.popupsAllowed = true;

/**
 * Editor inherits from mxEventSource
 */
mxUtils.extend(Editor, mxEventSource);

/**
 * Stores initial state of mxClient.NO_FO.
 */
Editor.prototype.originalNoForeignObject = mxClient.NO_FO;

/**
 * Specifies the image URL to be used for the transparent background.
 */
Editor.prototype.transparentImage = mxClient.IS_SVG
  ? 'data:image/gif;base64,R0lGODlhMAAwAIAAAP///wAAACH5BAEAAAAALAAAAAAwADAAAAIxhI+py+0Po5y02ouz3rz7D4biSJbmiabqyrbuC8fyTNf2jef6zvf+DwwKh8Si8egpAAA7'
  : IMAGE_PATH + '/transparent.gif';

/**
 * Specifies if the canvas should be extended in all directions. Default is true.
 */
Editor.prototype.extendCanvas = true;

/**
 * Specifies if the app should run in chromeless mode. Default is false.
 * This default is only used if the contructor argument is null.
 */
Editor.prototype.chromeless = false;

/**
 * Specifies the order of OK/Cancel buttons in dialogs. Default is true.
 * Cancel first is used on Macs, Windows/Confluence uses cancel last.
 */
Editor.prototype.cancelFirst = true;

/**
 * Specifies if the editor is enabled. Default is true.
 */
Editor.prototype.enabled = true;

/**
 * Contains the name which was used for the last save. Default value is null.
 */
Editor.prototype.filename = null;

/**
 * Contains the current modified state of the diagram. This is false for
 * new diagrams and after the diagram was saved.
 */
Editor.prototype.modified = false;

/**
 * Specifies if the diagram should be saved automatically if possible. Default
 * is true.
 */
Editor.prototype.autosave = true;

/**
 * Specifies the top spacing for the initial page view. Default is 0.
 */
Editor.prototype.initialTopSpacing = 0;

/**
 * Specifies the app name. Default is document.title.
 */
Editor.prototype.appName = document.title;

/**
 *
 */
Editor.prototype.editBlankUrl = window.location.protocol + '//' + window.location.host + '/';

/**
 * Default value for the graph container overflow style.
 */
Editor.prototype.defaultGraphOverflow = 'hidden';

/**
 * Initializes the environment.
 */
Editor.prototype.init = function () {};

/**
 * Sets the XML node for the current diagram.
 */
Editor.prototype.isChromelessView = function () {
  return this.chromeless;
};

/**
 * Sets the XML node for the current diagram.
 */
Editor.prototype.setAutosave = function (value) {
  this.autosave = value;
  this.fireEvent(new mxEventObject('autosaveChanged'));
};

/**
 *
 */
Editor.prototype.getEditBlankUrl = function (params) {
  return this.editBlankUrl + params;
};

/**
 *
 */
Editor.prototype.editAsNew = function (xml, title) {
  var p = title != null ? '?title=' + encodeURIComponent(title) : '';

  if (urlParams['ui'] != null) {
    p += (p.length > 0 ? '&' : '?') + 'ui=' + urlParams['ui'];
  }

  if (typeof window.postMessage !== 'undefined' && (document.documentMode == null || document.documentMode >= 10)) {
    var wnd = null;

    var l = mxUtils.bind(this, function (evt) {
      if (evt.data == 'ready' && evt.source == wnd) {
        mxEvent.removeListener(window, 'message', l);
        wnd.postMessage(xml, '*');
      }
    });

    mxEvent.addListener(window, 'message', l);
    wnd = this.graph.openLink(this.getEditBlankUrl(p + (p.length > 0 ? '&' : '?') + 'client=1'), null, true);
  } else {
    this.graph.openLink(this.getEditBlankUrl(p) + '#R' + encodeURIComponent(xml));
  }
};

/**
 * Sets the XML node for the current diagram.
 */
Editor.prototype.createGraph = function (themes, model) {
  var graph = new Graph(null, model, null, null, themes);
  graph.transparentBackground = false;

  // Opens all links in a new window while editing
  if (!this.chromeless) {
    graph.isBlankLink = function (href) {
      return !this.isExternalProtocol(href);
    };
  }

  return graph;
};

/**
 * Sets the XML node for the current diagram.
 */
Editor.prototype.resetGraph = function () {
  this.graph.gridEnabled = !this.isChromelessView() || urlParams['grid'] == '1';
  this.graph.graphHandler.guidesEnabled = true;
  this.graph.setTooltips(true);
  this.graph.setConnectable(true);
  this.graph.foldingEnabled = true;
  this.graph.scrollbars = this.graph.defaultScrollbars;
  this.graph.pageVisible = this.graph.defaultPageVisible;
  this.graph.pageBreaksVisible = this.graph.pageVisible;
  this.graph.preferPageSize = this.graph.pageBreaksVisible;
  this.graph.background = null;
  this.graph.pageScale = mxGraph.prototype.pageScale;
  this.graph.pageFormat = mxGraph.prototype.pageFormat;
  this.graph.currentScale = 1;
  this.graph.currentTranslate.x = 0;
  this.graph.currentTranslate.y = 0;
  this.updateGraphComponents();
  this.graph.view.setScale(1);
};

/**
 * Sets the XML node for the current diagram.
 */
Editor.prototype.readGraphState = function (node) {
  this.graph.gridEnabled = node.getAttribute('grid') != '0' && (!this.isChromelessView() || urlParams['grid'] == '1');
  this.graph.gridSize = parseFloat(node.getAttribute('gridSize')) || mxGraph.prototype.gridSize;
  this.graph.graphHandler.guidesEnabled = node.getAttribute('guides') != '0';
  this.graph.setTooltips(node.getAttribute('tooltips') != '0');
  this.graph.setConnectable(node.getAttribute('connect') != '0');
  this.graph.connectionArrowsEnabled = node.getAttribute('arrows') != '0';
  this.graph.foldingEnabled = node.getAttribute('fold') != '0';

  if (this.isChromelessView() && this.graph.foldingEnabled) {
    this.graph.foldingEnabled = urlParams['nav'] == '1';
    this.graph.cellRenderer.forceControlClickHandler = this.graph.foldingEnabled;
  }

  var ps = parseFloat(node.getAttribute('pageScale'));

  if (!isNaN(ps) && ps > 0) {
    this.graph.pageScale = ps;
  } else {
    this.graph.pageScale = mxGraph.prototype.pageScale;
  }

  if (!this.graph.isLightboxView() && !this.graph.isViewer()) {
    var pv = node.getAttribute('page');

    if (pv != null) {
      this.graph.pageVisible = pv != '0';
    } else {
      this.graph.pageVisible = this.graph.defaultPageVisible;
    }
  } else {
    this.graph.pageVisible = false;
  }

  this.graph.pageBreaksVisible = this.graph.pageVisible;
  this.graph.preferPageSize = this.graph.pageBreaksVisible;

  var pw = parseFloat(node.getAttribute('pageWidth'));
  var ph = parseFloat(node.getAttribute('pageHeight'));

  if (!isNaN(pw) && !isNaN(ph)) {
    this.graph.pageFormat = new mxRectangle(0, 0, pw, ph);
  }

  // Loads the persistent state settings
  var bg = node.getAttribute('background');

  if (bg != null && bg.length > 0) {
    this.graph.background = bg;
  } else {
    this.graph.background = null;
  }
};

/**
 * Sets the XML node for the current diagram.
 */
Editor.prototype.setGraphXml = function (node) {
  if (node != null) {
    var dec = new mxCodec(node.ownerDocument);

    if (node.nodeName == 'mxGraphModel') {
      this.graph.model.beginUpdate();

      try {
        this.graph.model.clear();
        this.graph.view.scale = 1;
        this.readGraphState(node);
        this.updateGraphComponents();
        dec.decode(node, this.graph.getModel());
      } finally {
        this.graph.model.endUpdate();
      }

      this.fireEvent(new mxEventObject('resetGraphView'));
    } else if (node.nodeName == 'root') {
      this.resetGraph();

      // Workaround for invalid XML output in Firefox 20 due to bug in mxUtils.getXml
      var wrapper = dec.document.createElement('mxGraphModel');
      wrapper.appendChild(node);

      dec.decode(wrapper, this.graph.getModel());
      this.updateGraphComponents();
      this.fireEvent(new mxEventObject('resetGraphView'));
    } else {
      throw {
        message: mxResources.get('cannotOpenFile'),
        node: node,
        toString: function () {
          return this.message;
        },
      };
    }
  } else {
    this.resetGraph();
    this.graph.model.clear();
    this.fireEvent(new mxEventObject('resetGraphView'));
  }
};

/**
 * Returns the XML node that represents the current diagram.
 */
Editor.prototype.getGraphXml = function (ignoreSelection) {
  ignoreSelection = ignoreSelection != null ? ignoreSelection : true;
  var node = null;

  if (ignoreSelection) {
    var enc = new mxCodec(mxUtils.createXmlDocument());
    node = enc.encode(this.graph.getModel());
  } else {
    node = this.graph.encodeCells(mxUtils.sortCells(this.graph.model.getTopmostCells(this.graph.getSelectionCells())));
  }

  if (this.graph.view.translate.x != 0 || this.graph.view.translate.y != 0) {
    node.setAttribute('dx', Math.round(this.graph.view.translate.x * 100) / 100);
    node.setAttribute('dy', Math.round(this.graph.view.translate.y * 100) / 100);
  }

  node.setAttribute('grid', this.graph.isGridEnabled() ? '1' : '0');
  node.setAttribute('gridSize', this.graph.gridSize);
  node.setAttribute('guides', this.graph.graphHandler.guidesEnabled ? '1' : '0');
  node.setAttribute('tooltips', this.graph.tooltipHandler.isEnabled() ? '1' : '0');
  node.setAttribute('connect', this.graph.connectionHandler.isEnabled() ? '1' : '0');
  node.setAttribute('arrows', this.graph.connectionArrowsEnabled ? '1' : '0');
  node.setAttribute('fold', this.graph.foldingEnabled ? '1' : '0');
  node.setAttribute('page', this.graph.pageVisible ? '1' : '0');
  node.setAttribute('pageScale', this.graph.pageScale);
  node.setAttribute('pageWidth', this.graph.pageFormat.width);
  node.setAttribute('pageHeight', this.graph.pageFormat.height);

  if (this.graph.background != null) {
    node.setAttribute('background', this.graph.background);
  }

  return node;
};

/**
 * Keeps the graph container in sync with the persistent graph state
 */
Editor.prototype.updateGraphComponents = function () {
  var graph = this.graph;

  if (graph.container != null) {
    graph.view.validateBackground();
    graph.container.style.overflow = graph.scrollbars ? 'auto' : this.defaultGraphOverflow;

    this.fireEvent(new mxEventObject('updateGraphComponents'));
  }
};

/**
 * Sets the modified flag.
 */
Editor.prototype.setModified = function (value) {
  this.modified = value;
};

/**
 * Sets the filename.
 */
Editor.prototype.setFilename = function (value) {
  this.filename = value;
};

/**
 * Creates and returns a new undo manager.
 */
Editor.prototype.createUndoManager = function () {
  var graph = this.graph;
  var undoMgr = new mxUndoManager();

  this.undoListener = function (sender, evt) {
    undoMgr.undoableEditHappened(evt.getProperty('edit'));
  };

  // Installs the command history
  var listener = mxUtils.bind(this, function (sender, evt) {
    this.undoListener.apply(this, arguments);
  });

  graph.getModel().addListener(mxEvent.UNDO, listener);
  graph.getView().addListener(mxEvent.UNDO, listener);

  // Keeps the selection in sync with the history
  var undoHandler = function (sender, evt) {
    var cand = graph.getSelectionCellsForChanges(evt.getProperty('edit').changes, function (change) {
      // Only selects changes to the cell hierarchy
      return !(change instanceof mxChildChange);
    });

    if (cand.length > 0) {
      var model = graph.getModel();
      var cells = [];

      for (var i = 0; i < cand.length; i++) {
        if (graph.view.getState(cand[i]) != null) {
          cells.push(cand[i]);
        }
      }

      graph.setSelectionCells(cells);
    }
  };

  undoMgr.addListener(mxEvent.UNDO, undoHandler);
  undoMgr.addListener(mxEvent.REDO, undoHandler);

  return undoMgr;
};

/**
 * Adds basic stencil set (no namespace).
 */
Editor.prototype.initStencilRegistry = function () {};

/**
 * Creates and returns a new undo manager.
 */
Editor.prototype.destroy = function () {
  if (this.graph != null) {
    this.graph.destroy();
    this.graph = null;
  }
};

/**
 * Class for asynchronously opening a new window and loading a file at the same
 * time. This acts as a bridge between the open dialog and the new editor.
 */
OpenFile = function (done) {
  this.producer = null;
  this.consumer = null;
  this.done = done;
  this.args = null;
};

/**
 * Registers the editor from the new window.
 */
OpenFile.prototype.setConsumer = function (value) {
  this.consumer = value;
  this.execute();
};

/**
 * Sets the data from the loaded file.
 */
OpenFile.prototype.setData = function () {
  this.args = arguments;
  this.execute();
};

/**
 * Displays an error message.
 */
OpenFile.prototype.error = function (msg) {
  this.cancel(true);
  mxUtils.alert(msg);
};

/**
 * Consumes the data.
 */
OpenFile.prototype.execute = function () {
  if (this.consumer != null && this.args != null) {
    this.cancel(false);
    this.consumer.apply(this, this.args);
  }
};

/**
 * Cancels the operation.
 */
OpenFile.prototype.cancel = function (cancel) {
  if (this.done != null) {
    this.done(cancel != null ? cancel : true);
  }
};

/**
 * Basic dialogs that are available in the viewer (print dialog).
 */
function Dialog(editorUi, elt, w, h, modal, closable, onClose, noScroll, transparent, onResize, ignoreBgClick) {
  var dx = 0;

  if (mxClient.IS_VML && (document.documentMode == null || document.documentMode < 8)) {
    // Adds padding as a workaround for box model in older IE versions
    // This needs to match the total padding of geDialog in CSS
    dx = 80;
  }

  w += dx;
  h += dx;

  var w0 = w;
  var h0 = h;

  var ds = mxUtils.getDocumentSize();

  // Workaround for print dialog offset in viewer lightbox
  if (window.innerHeight != null) {
    ds.height = window.innerHeight;
  }

  var dh = ds.height;
  var left = Math.max(1, Math.round((ds.width - w - 64) / 2));
  var top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));

  // Keeps window size inside available space
  if (!mxClient.IS_QUIRKS) {
    elt.style.maxHeight = '100%';
  }

  w = document.body != null ? Math.min(w, document.body.scrollWidth - 64) : w;
  h = Math.min(h, dh - 64);

  // Increments zIndex to put subdialogs and background over existing dialogs and background
  if (editorUi.dialogs.length > 0) {
    this.zIndex += editorUi.dialogs.length * 2;
  }

  if (this.bg == null) {
    this.bg = editorUi.createDiv('background');
    this.bg.style.position = 'absolute';
    this.bg.style.background = Dialog.backdropColor;
    this.bg.style.height = dh + 'px';
    this.bg.style.right = '0px';
    this.bg.style.zIndex = this.zIndex - 2;

    mxUtils.setOpacity(this.bg, this.bgOpacity);

    if (mxClient.IS_QUIRKS) {
      new mxDivResizer(this.bg);
    }
  }

  var origin = mxUtils.getDocumentScrollOrigin(document);
  this.bg.style.left = origin.x + 'px';
  this.bg.style.top = origin.y + 'px';
  left += origin.x;
  top += origin.y;

  if (modal) {
    document.body.appendChild(this.bg);
  }

  var div = editorUi.createDiv(transparent ? 'geTransDialog' : 'geDialog');
  var pos = this.getPosition(left, top, w, h);
  left = pos.x;
  top = pos.y;

  div.style.width = w + 'px';
  div.style.height = h + 4 + 'px';
  div.style.left = left + 'px';
  div.style.top = top + 'px';
  div.style.zIndex = this.zIndex;

  div.appendChild(elt);
  document.body.appendChild(div);

  // Adds vertical scrollbars if needed
  if (!noScroll && elt.clientHeight > div.clientHeight - 64) {
    elt.style.overflowY = 'auto';
  }

  if (closable) {
    var img = document.createElement('img');

    img.setAttribute('src', Dialog.prototype.closeImage);
    img.setAttribute('title', mxResources.get('close'));
    img.className = 'geDialogClose';
    img.style.top = top + 14 + 'px';
    img.style.left = left + w + 38 - dx + 'px';
    img.style.zIndex = this.zIndex;

    mxEvent.addListener(
      img,
      'click',
      mxUtils.bind(this, function () {
        editorUi.hideDialog(true);
      })
    );

    document.body.appendChild(img);
    this.dialogImg = img;

    if (!ignoreBgClick) {
      var mouseDownSeen = false;

      mxEvent.addGestureListeners(
        this.bg,
        mxUtils.bind(this, function (evt) {
          mouseDownSeen = true;
        }),
        null,
        mxUtils.bind(this, function (evt) {
          if (mouseDownSeen) {
            editorUi.hideDialog(true);
            mouseDownSeen = false;
          }
        })
      );
    }
  }

  this.resizeListener = mxUtils.bind(this, function () {
    if (onResize != null) {
      var newWH = onResize();

      if (newWH != null) {
        w0 = w = newWH.w;
        h0 = h = newWH.h;
      }
    }

    var ds = mxUtils.getDocumentSize();
    dh = ds.height;
    this.bg.style.height = dh + 'px';

    left = Math.max(1, Math.round((ds.width - w - 64) / 2));
    top = Math.max(1, Math.round((dh - h - editorUi.footerHeight) / 3));
    w = document.body != null ? Math.min(w0, document.body.scrollWidth - 64) : w0;
    h = Math.min(h0, dh - 64);

    var pos = this.getPosition(left, top, w, h);
    left = pos.x;
    top = pos.y;

    div.style.left = left + 'px';
    div.style.top = top + 'px';
    div.style.width = w + 'px';
    div.style.height = h + 'px';

    // Adds vertical scrollbars if needed
    if (!noScroll && elt.clientHeight > div.clientHeight - 64) {
      elt.style.overflowY = 'auto';
    }

    if (this.dialogImg != null) {
      this.dialogImg.style.top = top + 14 + 'px';
      this.dialogImg.style.left = left + w + 38 - dx + 'px';
    }
  });

  mxEvent.addListener(window, 'resize', this.resizeListener);

  this.onDialogClose = onClose;
  this.container = div;

  editorUi.editor.fireEvent(new mxEventObject('showDialog'));
}

/**
 *
 */
Dialog.backdropColor = 'white';

/**
 *
 */
Dialog.prototype.zIndex = mxPopupMenu.prototype.zIndex - 1;

/**
 *
 */
Dialog.prototype.noColorImage = !mxClient.IS_SVG
  ? IMAGE_PATH + '/nocolor.png'
  : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkEzRDlBMUUwODYxMTExRTFCMzA4RDdDMjJBMEMxRDM3IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkEzRDlBMUUxODYxMTExRTFCMzA4RDdDMjJBMEMxRDM3Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QTNEOUExREU4NjExMTFFMUIzMDhEN0MyMkEwQzFEMzciIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QTNEOUExREY4NjExMTFFMUIzMDhEN0MyMkEwQzFEMzciLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5xh3fmAAAABlBMVEX////MzMw46qqDAAAAGElEQVR42mJggAJGKGAYIIGBth8KAAIMAEUQAIElnLuQAAAAAElFTkSuQmCC';

/**
 *
 */
Dialog.prototype.closeImage = !mxClient.IS_SVG
  ? IMAGE_PATH + '/close.png'
  : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJAQMAAADaX5RTAAAABlBMVEV7mr3///+wksspAAAAAnRSTlP/AOW3MEoAAAAdSURBVAgdY9jXwCDDwNDRwHCwgeExmASygSL7GgB12QiqNHZZIwAAAABJRU5ErkJggg==';

/**
 *
 */
Dialog.prototype.clearImage = !mxClient.IS_SVG
  ? IMAGE_PATH + '/clear.gif'
  : 'data:image/gif;base64,R0lGODlhDQAKAIABAMDAwP///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUIzOEM1NzI4NjEyMTFFMUEzMkNDMUE3NjZERDE2QjIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUIzOEM1NzM4NjEyMTFFMUEzMkNDMUE3NjZERDE2QjIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QjM4QzU3MDg2MTIxMUUxQTMyQ0MxQTc2NkREMTZCMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QjM4QzU3MTg2MTIxMUUxQTMyQ0MxQTc2NkREMTZCMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAEALAAAAAANAAoAAAIXTGCJebD9jEOTqRlttXdrB32PJ2ncyRQAOw==';

/**
 *
 */
Dialog.prototype.lockedImage = !mxClient.IS_SVG
  ? IMAGE_PATH + '/locked.png'
  : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAMAAABhq6zVAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MzdDMDZCODExNzIxMTFFNUI0RTk5NTg4OTcyMUUyODEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MzdDMDZCODIxNzIxMTFFNUI0RTk5NTg4OTcyMUUyODEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozN0MwNkI3RjE3MjExMUU1QjRFOTk1ODg5NzIxRTI4MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozN0MwNkI4MDE3MjExMUU1QjRFOTk1ODg5NzIxRTI4MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvqMCFYAAAAVUExURZmZmb+/v7KysqysrMzMzLGxsf///4g8N1cAAAAHdFJOU////////wAaSwNGAAAAPElEQVR42lTMQQ4AIQgEwUa0//9kTQirOweYOgDqAMbZUr10AGlAwx4/BJ2QJ4U0L5brYjovvpv32xZgAHZaATFtMbu4AAAAAElFTkSuQmCC';

/**
 *
 */
Dialog.prototype.unlockedImage = !mxClient.IS_SVG
  ? IMAGE_PATH + '/unlocked.png'
  : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAMAAABhq6zVAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MzdDMDZCN0QxNzIxMTFFNUI0RTk5NTg4OTcyMUUyODEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MzdDMDZCN0UxNzIxMTFFNUI0RTk5NTg4OTcyMUUyODEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozN0MwNkI3QjE3MjExMUU1QjRFOTk1ODg5NzIxRTI4MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozN0MwNkI3QzE3MjExMUU1QjRFOTk1ODg5NzIxRTI4MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkKMpVwAAAAYUExURZmZmbKysr+/v6ysrOXl5czMzLGxsf///zHN5lwAAAAIdFJOU/////////8A3oO9WQAAADxJREFUeNpUzFESACAEBNBVsfe/cZJU+8Mzs8CIABCidtfGOndnYsT40HDSiCcbPdoJo10o9aI677cpwACRoAF3dFNlswAAAABJRU5ErkJggg==';

/**
 * Removes the dialog from the DOM.
 */
Dialog.prototype.bgOpacity = 80;

/**
 * Removes the dialog from the DOM.
 */
Dialog.prototype.getPosition = function (left, top) {
  return new mxPoint(left, top);
};

/**
 * Removes the dialog from the DOM.
 */
Dialog.prototype.close = function (cancel, isEsc) {
  if (this.onDialogClose != null) {
    if (this.onDialogClose(cancel, isEsc) == false) {
      return false;
    }

    this.onDialogClose = null;
  }

  if (this.dialogImg != null) {
    this.dialogImg.parentNode.removeChild(this.dialogImg);
    this.dialogImg = null;
  }

  if (this.bg != null && this.bg.parentNode != null) {
    this.bg.parentNode.removeChild(this.bg);
  }

  mxEvent.removeListener(window, 'resize', this.resizeListener);
  this.container.parentNode.removeChild(this.container);
};

/**
 *
 */
var ErrorDialog = function (editorUi, title, message, buttonText, fn, retry, buttonText2, fn2, hide, buttonText3, fn3) {
  hide = hide != null ? hide : true;

  var div = document.createElement('div');
  div.style.textAlign = 'center';

  if (title != null) {
    var hd = document.createElement('div');
    hd.style.padding = '0px';
    hd.style.margin = '0px';
    hd.style.fontSize = '18px';
    hd.style.paddingBottom = '16px';
    hd.style.marginBottom = '10px';
    hd.style.borderBottom = '1px solid #c0c0c0';
    hd.style.color = 'gray';
    hd.style.whiteSpace = 'nowrap';
    hd.style.textOverflow = 'ellipsis';
    hd.style.overflow = 'hidden';
    mxUtils.write(hd, title);
    hd.setAttribute('title', title);
    div.appendChild(hd);
  }

  var p2 = document.createElement('div');
  p2.style.lineHeight = '1.2em';
  p2.style.padding = '6px';
  p2.innerHTML = message;
  div.appendChild(p2);

  var btns = document.createElement('div');
  btns.style.marginTop = '12px';
  btns.style.textAlign = 'center';

  if (retry != null) {
    var retryBtn = mxUtils.button(mxResources.get('tryAgain'), function () {
      editorUi.hideDialog();
      retry();
    });
    retryBtn.className = 'geBtn';
    btns.appendChild(retryBtn);

    btns.style.textAlign = 'center';
  }

  if (buttonText3 != null) {
    var btn3 = mxUtils.button(buttonText3, function () {
      if (fn3 != null) {
        fn3();
      }
    });

    btn3.className = 'geBtn';
    btns.appendChild(btn3);
  }

  var btn = mxUtils.button(buttonText, function () {
    if (hide) {
      editorUi.hideDialog();
    }

    if (fn != null) {
      fn();
    }
  });

  btn.className = 'geBtn';
  btns.appendChild(btn);

  if (buttonText2 != null) {
    var mainBtn = mxUtils.button(buttonText2, function () {
      if (hide) {
        editorUi.hideDialog();
      }

      if (fn2 != null) {
        fn2();
      }
    });

    mainBtn.className = 'geBtn gePrimaryBtn';
    btns.appendChild(mainBtn);
  }

  this.init = function () {
    btn.focus();
  };

  div.appendChild(btns);

  this.container = div;
};

/**
 * Constructs a new print dialog.
 */
var PrintDialog = function (editorUi, title) {
  this.create(editorUi, title);
};

/**
 * Constructs a new print dialog.
 */
PrintDialog.prototype.create = function (editorUi) {
  var graph = editorUi.editor.graph;
  var row, td;

  var table = document.createElement('table');
  table.style.width = '100%';
  table.style.height = '100%';
  var tbody = document.createElement('tbody');

  row = document.createElement('tr');

  var onePageCheckBox = document.createElement('input');
  onePageCheckBox.setAttribute('type', 'checkbox');
  td = document.createElement('td');
  td.setAttribute('colspan', '2');
  td.style.fontSize = '10pt';
  td.appendChild(onePageCheckBox);

  var span = document.createElement('span');
  mxUtils.write(span, ' ' + mxResources.get('fitPage'));
  td.appendChild(span);

  mxEvent.addListener(span, 'click', function (evt) {
    onePageCheckBox.checked = !onePageCheckBox.checked;
    pageCountCheckBox.checked = !onePageCheckBox.checked;
    mxEvent.consume(evt);
  });

  mxEvent.addListener(onePageCheckBox, 'change', function () {
    pageCountCheckBox.checked = !onePageCheckBox.checked;
  });

  row.appendChild(td);
  tbody.appendChild(row);

  row = row.cloneNode(false);

  var pageCountCheckBox = document.createElement('input');
  pageCountCheckBox.setAttribute('type', 'checkbox');
  td = document.createElement('td');
  td.style.fontSize = '10pt';
  td.appendChild(pageCountCheckBox);

  var span = document.createElement('span');
  mxUtils.write(span, ' ' + mxResources.get('posterPrint') + ':');
  td.appendChild(span);

  mxEvent.addListener(span, 'click', function (evt) {
    pageCountCheckBox.checked = !pageCountCheckBox.checked;
    onePageCheckBox.checked = !pageCountCheckBox.checked;
    mxEvent.consume(evt);
  });

  row.appendChild(td);

  var pageCountInput = document.createElement('input');
  pageCountInput.setAttribute('value', '1');
  pageCountInput.setAttribute('type', 'number');
  pageCountInput.setAttribute('min', '1');
  pageCountInput.setAttribute('size', '4');
  pageCountInput.setAttribute('disabled', 'disabled');
  pageCountInput.style.width = '50px';

  td = document.createElement('td');
  td.style.fontSize = '10pt';
  td.appendChild(pageCountInput);
  mxUtils.write(td, ' ' + mxResources.get('pages') + ' (max)');
  row.appendChild(td);
  tbody.appendChild(row);

  mxEvent.addListener(pageCountCheckBox, 'change', function () {
    if (pageCountCheckBox.checked) {
      pageCountInput.removeAttribute('disabled');
    } else {
      pageCountInput.setAttribute('disabled', 'disabled');
    }

    onePageCheckBox.checked = !pageCountCheckBox.checked;
  });

  row = row.cloneNode(false);

  td = document.createElement('td');
  mxUtils.write(td, mxResources.get('pageScale') + ':');
  row.appendChild(td);

  td = document.createElement('td');
  var pageScaleInput = document.createElement('input');
  pageScaleInput.setAttribute('value', '100 %');
  pageScaleInput.setAttribute('size', '5');
  pageScaleInput.style.width = '50px';

  td.appendChild(pageScaleInput);
  row.appendChild(td);
  tbody.appendChild(row);

  row = document.createElement('tr');
  td = document.createElement('td');
  td.colSpan = 2;
  td.style.paddingTop = '20px';
  td.setAttribute('align', 'right');

  // Overall scale for print-out to account for print borders in dialogs etc
  function preview(print) {
    var autoOrigin = onePageCheckBox.checked || pageCountCheckBox.checked;
    var printScale = parseInt(pageScaleInput.value) / 100;

    if (isNaN(printScale)) {
      printScale = 1;
      pageScaleInput.value = '100%';
    }

    // Workaround to match available paper size in actual print output
    printScale *= 0.75;

    var pf = graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT;
    var scale = 1 / graph.pageScale;

    if (autoOrigin) {
      var pageCount = onePageCheckBox.checked ? 1 : parseInt(pageCountInput.value);

      if (!isNaN(pageCount)) {
        scale = mxUtils.getScaleForPageCount(pageCount, graph, pf);
      }
    }

    // Negative coordinates are cropped or shifted if page visible
    var gb = graph.getGraphBounds();
    var border = 0;
    var x0 = 0;
    var y0 = 0;

    // Applies print scale
    pf = mxRectangle.fromRectangle(pf);
    pf.width = Math.ceil(pf.width * printScale);
    pf.height = Math.ceil(pf.height * printScale);
    scale *= printScale;

    // Starts at first visible page
    if (!autoOrigin && graph.pageVisible) {
      var layout = graph.getPageLayout();
      x0 -= layout.x * pf.width;
      y0 -= layout.y * pf.height;
    } else {
      autoOrigin = true;
    }

    var preview = PrintDialog.createPrintPreview(graph, scale, pf, border, x0, y0, autoOrigin);
    preview.open();

    if (print) {
      PrintDialog.printPreview(preview);
    }
  }

  var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
    editorUi.hideDialog();
  });
  cancelBtn.className = 'geBtn';

  if (editorUi.editor.cancelFirst) {
    td.appendChild(cancelBtn);
  }

  if (PrintDialog.previewEnabled) {
    var previewBtn = mxUtils.button(mxResources.get('preview'), function () {
      editorUi.hideDialog();
      preview(false);
    });
    previewBtn.className = 'geBtn';
    td.appendChild(previewBtn);
  }

  var printBtn = mxUtils.button(mxResources.get(!PrintDialog.previewEnabled ? 'ok' : 'print'), function () {
    editorUi.hideDialog();
    preview(true);
  });
  printBtn.className = 'geBtn gePrimaryBtn';
  td.appendChild(printBtn);

  if (!editorUi.editor.cancelFirst) {
    td.appendChild(cancelBtn);
  }

  row.appendChild(td);
  tbody.appendChild(row);

  table.appendChild(tbody);
  this.container = table;
};

/**
 * Constructs a new print dialog.
 */
PrintDialog.printPreview = function (preview) {
  try {
    if (preview.wnd != null) {
      var printFn = function () {
        preview.wnd.focus();
        preview.wnd.print();
        preview.wnd.close();
      };

      // Workaround for Google Chrome which needs a bit of a
      // delay in order to render the SVG contents
      // Needs testing in production
      if (mxClient.IS_GC) {
        window.setTimeout(printFn, 500);
      } else {
        printFn();
      }
    }
  } catch (e) {
    // ignores possible Access Denied
  }
};

/**
 * Constructs a new print dialog.
 */
PrintDialog.createPrintPreview = function (graph, scale, pf, border, x0, y0, autoOrigin) {
  var preview = new mxPrintPreview(graph, scale, pf, border, x0, y0);
  preview.title = mxResources.get('preview');
  preview.printBackgroundImage = true;
  preview.autoOrigin = autoOrigin;
  var bg = graph.background;

  if (bg == null || bg == '' || bg == mxConstants.NONE) {
    bg = '#ffffff';
  }

  preview.backgroundColor = bg;

  var writeHead = preview.writeHead;

  // Adds a border in the preview
  preview.writeHead = function (doc) {
    writeHead.apply(this, arguments);

    doc.writeln('<style type="text/css">');
    doc.writeln('@media screen {');
    doc.writeln('  body > div { padding:30px;box-sizing:content-box; }');
    doc.writeln('}');
    doc.writeln('</style>');
  };

  return preview;
};

/**
 * Specifies if the preview button should be enabled. Default is true.
 */
PrintDialog.previewEnabled = true;

/**
 * Constructs a new page setup dialog.
 */
var PageSetupDialog = function (editorUi) {
  var graph = editorUi.editor.graph;
  var row, td;

  var table = document.createElement('table');
  table.style.width = '100%';
  table.style.height = '100%';
  var tbody = document.createElement('tbody');

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.verticalAlign = 'top';
  td.style.fontSize = '10pt';
  mxUtils.write(td, mxResources.get('paperSize') + ':');

  row.appendChild(td);

  td = document.createElement('td');
  td.style.verticalAlign = 'top';
  td.style.fontSize = '10pt';

  var accessor = PageSetupDialog.addPageFormatPanel(td, 'pagesetupdialog', graph.pageFormat);

  row.appendChild(td);
  tbody.appendChild(row);

  row = document.createElement('tr');

  td = document.createElement('td');
  mxUtils.write(td, mxResources.get('background') + ':');

  row.appendChild(td);

  td = document.createElement('td');
  td.style.whiteSpace = 'nowrap';

  var backgroundInput = document.createElement('input');
  backgroundInput.setAttribute('type', 'text');
  var backgroundButton = document.createElement('button');

  backgroundButton.style.width = '18px';
  backgroundButton.style.height = '18px';
  backgroundButton.style.marginRight = '20px';
  backgroundButton.style.backgroundPosition = 'center center';
  backgroundButton.style.backgroundRepeat = 'no-repeat';

  var newBackgroundColor = graph.background;

  function updateBackgroundColor() {
    if (newBackgroundColor == null || newBackgroundColor == mxConstants.NONE) {
      backgroundButton.style.backgroundColor = '';
      backgroundButton.style.backgroundImage = "url('" + Dialog.prototype.noColorImage + "')";
    } else {
      backgroundButton.style.backgroundColor = newBackgroundColor;
      backgroundButton.style.backgroundImage = '';
    }
  }

  updateBackgroundColor();

  mxEvent.addListener(backgroundButton, 'click', function (evt) {
    editorUi.pickColor(newBackgroundColor || 'none', function (color) {
      newBackgroundColor = color;
      updateBackgroundColor();
    });
    mxEvent.consume(evt);
  });

  td.appendChild(backgroundButton);

  mxUtils.write(td, mxResources.get('gridSize') + ':');

  var gridSizeInput = document.createElement('input');
  gridSizeInput.setAttribute('type', 'number');
  gridSizeInput.setAttribute('min', '0');
  gridSizeInput.style.width = '40px';
  gridSizeInput.style.marginLeft = '6px';

  gridSizeInput.value = graph.getGridSize();
  td.appendChild(gridSizeInput);

  mxEvent.addListener(gridSizeInput, 'change', function () {
    var value = parseInt(gridSizeInput.value);
    gridSizeInput.value = Math.max(1, isNaN(value) ? graph.getGridSize() : value);
  });

  row.appendChild(td);
  tbody.appendChild(row);

  row = document.createElement('tr');
  td = document.createElement('td');

  mxUtils.write(td, mxResources.get('image') + ':');

  row.appendChild(td);
  td = document.createElement('td');

  var changeImageLink = document.createElement('a');
  changeImageLink.style.textDecoration = 'underline';
  changeImageLink.style.cursor = 'pointer';
  changeImageLink.style.color = '#a0a0a0';

  var newBackgroundImage = graph.backgroundImage;

  function updateBackgroundImage() {
    if (newBackgroundImage == null) {
      changeImageLink.removeAttribute('title');
      changeImageLink.style.fontSize = '';
      changeImageLink.innerHTML = mxUtils.htmlEntities(mxResources.get('change')) + '...';
    } else {
      changeImageLink.setAttribute('title', newBackgroundImage.src);
      changeImageLink.style.fontSize = '11px';
      changeImageLink.innerHTML = mxUtils.htmlEntities(newBackgroundImage.src.substring(0, 42)) + '...';
    }
  }

  mxEvent.addListener(changeImageLink, 'click', function (evt) {
    editorUi.showBackgroundImageDialog(function (image, failed) {
      if (!failed) {
        newBackgroundImage = image;
        updateBackgroundImage();
      }
    }, newBackgroundImage);

    mxEvent.consume(evt);
  });

  updateBackgroundImage();

  td.appendChild(changeImageLink);

  row.appendChild(td);
  tbody.appendChild(row);

  row = document.createElement('tr');
  td = document.createElement('td');
  td.colSpan = 2;
  td.style.paddingTop = '16px';
  td.setAttribute('align', 'right');

  var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
    editorUi.hideDialog();
  });
  cancelBtn.className = 'geBtn';

  if (editorUi.editor.cancelFirst) {
    td.appendChild(cancelBtn);
  }

  var applyBtn = mxUtils.button(mxResources.get('apply'), function () {
    editorUi.hideDialog();
    var gridSize = parseInt(gridSizeInput.value);

    if (!isNaN(gridSize) && graph.gridSize !== gridSize) {
      graph.setGridSize(gridSize);
    }

    var change = new ChangePageSetup(editorUi, newBackgroundColor, newBackgroundImage, accessor.get());
    change.ignoreColor = graph.background == newBackgroundColor;

    var oldSrc = graph.backgroundImage != null ? graph.backgroundImage.src : null;
    var newSrc = newBackgroundImage != null ? newBackgroundImage.src : null;

    change.ignoreImage = oldSrc === newSrc;

    if (
      graph.pageFormat.width != change.previousFormat.width ||
      graph.pageFormat.height != change.previousFormat.height ||
      !change.ignoreColor ||
      !change.ignoreImage
    ) {
      graph.model.execute(change);
    }
  });
  applyBtn.className = 'geBtn gePrimaryBtn';
  td.appendChild(applyBtn);

  if (!editorUi.editor.cancelFirst) {
    td.appendChild(cancelBtn);
  }

  row.appendChild(td);
  tbody.appendChild(row);

  table.appendChild(tbody);
  this.container = table;
};

/**
 *
 */
PageSetupDialog.addPageFormatPanel = function (div, namePostfix, pageFormat, pageFormatListener) {
  var formatName = 'format-' + namePostfix;

  var portraitCheckBox = document.createElement('input');
  portraitCheckBox.setAttribute('name', formatName);
  portraitCheckBox.setAttribute('type', 'radio');
  portraitCheckBox.setAttribute('value', 'portrait');

  var landscapeCheckBox = document.createElement('input');
  landscapeCheckBox.setAttribute('name', formatName);
  landscapeCheckBox.setAttribute('type', 'radio');
  landscapeCheckBox.setAttribute('value', 'landscape');

  var paperSizeSelect = document.createElement('select');
  paperSizeSelect.style.marginBottom = '8px';
  paperSizeSelect.style.width = '202px';

  var formatDiv = document.createElement('div');
  formatDiv.style.marginLeft = '4px';
  formatDiv.style.width = '210px';
  formatDiv.style.height = '24px';

  portraitCheckBox.style.marginRight = '6px';
  formatDiv.appendChild(portraitCheckBox);

  var portraitSpan = document.createElement('span');
  portraitSpan.style.maxWidth = '100px';
  mxUtils.write(portraitSpan, mxResources.get('portrait'));
  formatDiv.appendChild(portraitSpan);

  landscapeCheckBox.style.marginLeft = '10px';
  landscapeCheckBox.style.marginRight = '6px';
  formatDiv.appendChild(landscapeCheckBox);

  var landscapeSpan = document.createElement('span');
  landscapeSpan.style.width = '100px';
  mxUtils.write(landscapeSpan, mxResources.get('landscape'));
  formatDiv.appendChild(landscapeSpan);

  var customDiv = document.createElement('div');
  customDiv.style.marginLeft = '4px';
  customDiv.style.width = '210px';
  customDiv.style.height = '24px';

  var widthInput = document.createElement('input');
  widthInput.setAttribute('size', '7');
  widthInput.style.textAlign = 'right';
  customDiv.appendChild(widthInput);
  mxUtils.write(customDiv, ' in x ');

  var heightInput = document.createElement('input');
  heightInput.setAttribute('size', '7');
  heightInput.style.textAlign = 'right';
  customDiv.appendChild(heightInput);
  mxUtils.write(customDiv, ' in');

  formatDiv.style.display = 'none';
  customDiv.style.display = 'none';

  var pf = new Object();
  var formats = PageSetupDialog.getFormats();

  for (var i = 0; i < formats.length; i++) {
    var f = formats[i];
    pf[f.key] = f;

    var paperSizeOption = document.createElement('option');
    paperSizeOption.setAttribute('value', f.key);
    mxUtils.write(paperSizeOption, f.title);
    paperSizeSelect.appendChild(paperSizeOption);
  }

  var customSize = false;

  function listener(sender, evt, force) {
    if (force || (widthInput != document.activeElement && heightInput != document.activeElement)) {
      var detected = false;

      for (var i = 0; i < formats.length; i++) {
        var f = formats[i];

        // Special case where custom was chosen
        if (customSize) {
          if (f.key == 'custom') {
            paperSizeSelect.value = f.key;
            customSize = false;
          }
        } else if (f.format != null) {
          // Fixes wrong values for previous A4 and A5 page sizes
          if (f.key == 'a4') {
            if (pageFormat.width == 826) {
              pageFormat = mxRectangle.fromRectangle(pageFormat);
              pageFormat.width = 827;
            } else if (pageFormat.height == 826) {
              pageFormat = mxRectangle.fromRectangle(pageFormat);
              pageFormat.height = 827;
            }
          } else if (f.key == 'a5') {
            if (pageFormat.width == 584) {
              pageFormat = mxRectangle.fromRectangle(pageFormat);
              pageFormat.width = 583;
            } else if (pageFormat.height == 584) {
              pageFormat = mxRectangle.fromRectangle(pageFormat);
              pageFormat.height = 583;
            }
          }

          if (pageFormat.width == f.format.width && pageFormat.height == f.format.height) {
            paperSizeSelect.value = f.key;
            portraitCheckBox.setAttribute('checked', 'checked');
            portraitCheckBox.defaultChecked = true;
            portraitCheckBox.checked = true;
            landscapeCheckBox.removeAttribute('checked');
            landscapeCheckBox.defaultChecked = false;
            landscapeCheckBox.checked = false;
            detected = true;
          } else if (pageFormat.width == f.format.height && pageFormat.height == f.format.width) {
            paperSizeSelect.value = f.key;
            portraitCheckBox.removeAttribute('checked');
            portraitCheckBox.defaultChecked = false;
            portraitCheckBox.checked = false;
            landscapeCheckBox.setAttribute('checked', 'checked');
            landscapeCheckBox.defaultChecked = true;
            landscapeCheckBox.checked = true;
            detected = true;
          }
        }
      }

      // Selects custom format which is last in list
      if (!detected) {
        widthInput.value = pageFormat.width / 100;
        heightInput.value = pageFormat.height / 100;
        portraitCheckBox.setAttribute('checked', 'checked');
        paperSizeSelect.value = 'custom';
        formatDiv.style.display = 'none';
        customDiv.style.display = '';
      } else {
        formatDiv.style.display = '';
        customDiv.style.display = 'none';
      }
    }
  }

  listener();

  div.appendChild(paperSizeSelect);
  mxUtils.br(div);

  div.appendChild(formatDiv);
  div.appendChild(customDiv);

  var currentPageFormat = pageFormat;

  var update = function (evt, selectChanged) {
    var f = pf[paperSizeSelect.value];

    if (f.format != null) {
      widthInput.value = f.format.width / 100;
      heightInput.value = f.format.height / 100;
      customDiv.style.display = 'none';
      formatDiv.style.display = '';
    } else {
      formatDiv.style.display = 'none';
      customDiv.style.display = '';
    }

    var wi = parseFloat(widthInput.value);

    if (isNaN(wi) || wi <= 0) {
      widthInput.value = pageFormat.width / 100;
    }

    var hi = parseFloat(heightInput.value);

    if (isNaN(hi) || hi <= 0) {
      heightInput.value = pageFormat.height / 100;
    }

    var newPageFormat = new mxRectangle(
      0,
      0,
      Math.floor(parseFloat(widthInput.value) * 100),
      Math.floor(parseFloat(heightInput.value) * 100)
    );

    if (paperSizeSelect.value != 'custom' && landscapeCheckBox.checked) {
      newPageFormat = new mxRectangle(0, 0, newPageFormat.height, newPageFormat.width);
    }

    // Initial select of custom should not update page format to avoid update of combo
    if (
      (!selectChanged || !customSize) &&
      (newPageFormat.width != currentPageFormat.width || newPageFormat.height != currentPageFormat.height)
    ) {
      currentPageFormat = newPageFormat;

      // Updates page format and reloads format panel
      if (pageFormatListener != null) {
        pageFormatListener(currentPageFormat);
      }
    }
  };

  mxEvent.addListener(portraitSpan, 'click', function (evt) {
    portraitCheckBox.checked = true;
    update(evt);
    mxEvent.consume(evt);
  });

  mxEvent.addListener(landscapeSpan, 'click', function (evt) {
    landscapeCheckBox.checked = true;
    update(evt);
    mxEvent.consume(evt);
  });

  mxEvent.addListener(widthInput, 'blur', update);
  mxEvent.addListener(widthInput, 'click', update);
  mxEvent.addListener(heightInput, 'blur', update);
  mxEvent.addListener(heightInput, 'click', update);
  mxEvent.addListener(landscapeCheckBox, 'change', update);
  mxEvent.addListener(portraitCheckBox, 'change', update);
  mxEvent.addListener(paperSizeSelect, 'change', function (evt) {
    // Handles special case where custom was chosen
    customSize = paperSizeSelect.value == 'custom';
    update(evt, true);
  });

  update();

  return {
    set: function (value) {
      pageFormat = value;
      listener(null, null, true);
    },
    get: function () {
      return currentPageFormat;
    },
    widthInput: widthInput,
    heightInput: heightInput,
  };
};

/**
 *
 */
PageSetupDialog.getFormats = function () {
  return [
    { key: 'letter', title: 'US-Letter (8,5" x 11")', format: mxConstants.PAGE_FORMAT_LETTER_PORTRAIT },
    { key: 'legal', title: 'US-Legal (8,5" x 14")', format: new mxRectangle(0, 0, 850, 1400) },
    { key: 'tabloid', title: 'US-Tabloid (11" x 17")', format: new mxRectangle(0, 0, 1100, 1700) },
    { key: 'executive', title: 'US-Executive (7" x 10")', format: new mxRectangle(0, 0, 700, 1000) },
    { key: 'a0', title: 'A0 (841 mm x 1189 mm)', format: new mxRectangle(0, 0, 3300, 4681) },
    { key: 'a1', title: 'A1 (594 mm x 841 mm)', format: new mxRectangle(0, 0, 2339, 3300) },
    { key: 'a2', title: 'A2 (420 mm x 594 mm)', format: new mxRectangle(0, 0, 1654, 2336) },
    { key: 'a3', title: 'A3 (297 mm x 420 mm)', format: new mxRectangle(0, 0, 1169, 1654) },
    { key: 'a4', title: 'A4 (210 mm x 297 mm)', format: mxConstants.PAGE_FORMAT_A4_PORTRAIT },
    { key: 'a5', title: 'A5 (148 mm x 210 mm)', format: new mxRectangle(0, 0, 583, 827) },
    { key: 'a6', title: 'A6 (105 mm x 148 mm)', format: new mxRectangle(0, 0, 413, 583) },
    { key: 'a7', title: 'A7 (74 mm x 105 mm)', format: new mxRectangle(0, 0, 291, 413) },
    { key: 'b4', title: 'B4 (250 mm x 353 mm)', format: new mxRectangle(0, 0, 980, 1390) },
    { key: 'b5', title: 'B5 (176 mm x 250 mm)', format: new mxRectangle(0, 0, 690, 980) },
    { key: '16-9', title: '16:9 (1600 x 900)', format: new mxRectangle(0, 0, 900, 1600) },
    { key: '16-10', title: '16:10 (1920 x 1200)', format: new mxRectangle(0, 0, 1200, 1920) },
    { key: '4-3', title: '4:3 (1600 x 1200)', format: new mxRectangle(0, 0, 1200, 1600) },
    { key: 'custom', title: mxResources.get('custom'), format: null },
  ];
};

/**
 * Constructs a new filename dialog.
 */
var FilenameDialog = function (
  editorUi,
  filename,
  buttonText,
  fn,
  label,
  validateFn,
  content,
  helpLink,
  closeOnBtn,
  cancelFn,
  hints,
  w
) {
  closeOnBtn = closeOnBtn != null ? closeOnBtn : true;
  var row, td;

  var table = document.createElement('table');
  var tbody = document.createElement('tbody');
  table.style.marginTop = '8px';

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.whiteSpace = 'nowrap';
  td.style.fontSize = '10pt';
  td.style.width = hints ? '80px' : '120px';
  mxUtils.write(td, (label || mxResources.get('filename')) + ':');

  row.appendChild(td);

  var nameInput = document.createElement('input');
  nameInput.setAttribute('value', filename || '');
  nameInput.style.marginLeft = '4px';
  nameInput.style.width = w != null ? w + 'px' : '180px';

  var genericBtn = mxUtils.button(buttonText, function () {
    if (validateFn == null || validateFn(nameInput.value)) {
      if (closeOnBtn) {
        editorUi.hideDialog();
      }

      fn(nameInput.value);
    }
  });
  genericBtn.className = 'geBtn gePrimaryBtn';

  this.init = function () {
    if (label == null && content != null) {
      return;
    }

    nameInput.focus();

    if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5 || mxClient.IS_QUIRKS) {
      nameInput.select();
    } else {
      document.execCommand('selectAll', false, null);
    }

    // Installs drag and drop handler for links
    if (Graph.fileSupport) {
      // Setup the dnd listeners
      var dlg = table.parentNode;

      if (dlg != null) {
        var graph = editorUi.editor.graph;
        var dropElt = null;

        mxEvent.addListener(dlg, 'dragleave', function (evt) {
          if (dropElt != null) {
            dropElt.style.backgroundColor = '';
            dropElt = null;
          }

          evt.stopPropagation();
          evt.preventDefault();
        });

        mxEvent.addListener(
          dlg,
          'dragover',
          mxUtils.bind(this, function (evt) {
            // IE 10 does not implement pointer-events so it can't have a drop highlight
            if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10)) {
              dropElt = nameInput;
              dropElt.style.backgroundColor = '#ebf2f9';
            }

            evt.stopPropagation();
            evt.preventDefault();
          })
        );

        mxEvent.addListener(
          dlg,
          'drop',
          mxUtils.bind(this, function (evt) {
            if (dropElt != null) {
              dropElt.style.backgroundColor = '';
              dropElt = null;
            }

            if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0) {
              nameInput.value = decodeURIComponent(evt.dataTransfer.getData('text/uri-list'));
              genericBtn.click();
            }

            evt.stopPropagation();
            evt.preventDefault();
          })
        );
      }
    }
  };

  td = document.createElement('td');
  td.style.whiteSpace = 'nowrap';
  td.appendChild(nameInput);
  row.appendChild(td);

  if (label != null || content == null) {
    tbody.appendChild(row);

    if (hints != null) {
      if (editorUi.editor.diagramFileTypes != null) {
        var typeSelect = FilenameDialog.createFileTypes(editorUi, nameInput, editorUi.editor.diagramFileTypes);
        typeSelect.style.marginLeft = '6px';
        typeSelect.style.width = '74px';

        td.appendChild(typeSelect);
        nameInput.style.width = w != null ? w - 40 + 'px' : '140px';
      }

      td.appendChild(FilenameDialog.createTypeHint(editorUi, nameInput, hints));
    }
  }

  if (content != null) {
    row = document.createElement('tr');
    td = document.createElement('td');
    td.colSpan = 2;
    td.appendChild(content);
    row.appendChild(td);
    tbody.appendChild(row);
  }

  row = document.createElement('tr');
  td = document.createElement('td');
  td.colSpan = 2;
  td.style.paddingTop = '20px';
  td.style.whiteSpace = 'nowrap';
  td.setAttribute('align', 'right');

  var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
    editorUi.hideDialog();

    if (cancelFn != null) {
      cancelFn();
    }
  });
  cancelBtn.className = 'geBtn';

  if (editorUi.editor.cancelFirst) {
    td.appendChild(cancelBtn);
  }

  if (helpLink != null) {
    var helpBtn = mxUtils.button(mxResources.get('help'), function () {
      editorUi.editor.graph.openLink(helpLink);
    });

    helpBtn.className = 'geBtn';
    td.appendChild(helpBtn);
  }

  mxEvent.addListener(nameInput, 'keypress', function (e) {
    if (e.keyCode == 13) {
      genericBtn.click();
    }
  });

  td.appendChild(genericBtn);

  if (!editorUi.editor.cancelFirst) {
    td.appendChild(cancelBtn);
  }

  row.appendChild(td);
  tbody.appendChild(row);
  table.appendChild(tbody);

  this.container = table;
};

/**
 *
 */
FilenameDialog.filenameHelpLink = null;

/**
 *
 */
FilenameDialog.createTypeHint = function (ui, nameInput, hints) {
  var hint = document.createElement('img');
  hint.style.cssText =
    'vertical-align:top;height:16px;width:16px;margin-left:4px;background-repeat:no-repeat;background-position:center bottom;cursor:pointer;';
  mxUtils.setOpacity(hint, 70);

  var nameChanged = function () {
    hint.setAttribute('src', Editor.helpImage);
    hint.setAttribute('title', mxResources.get('help'));

    for (var i = 0; i < hints.length; i++) {
      if (
        hints[i].ext.length > 0 &&
        nameInput.value.toLowerCase().substring(nameInput.value.length - hints[i].ext.length - 1) == '.' + hints[i].ext
      ) {
        hint.setAttribute('src', mxClient.imageBasePath + '/warning.png');
        hint.setAttribute('title', mxResources.get(hints[i].title));
        break;
      }
    }
  };

  mxEvent.addListener(nameInput, 'keyup', nameChanged);
  mxEvent.addListener(nameInput, 'change', nameChanged);
  mxEvent.addListener(hint, 'click', function (evt) {
    var title = hint.getAttribute('title');

    if (hint.getAttribute('src') == Editor.helpImage) {
      ui.editor.graph.openLink(FilenameDialog.filenameHelpLink);
    } else if (title != '') {
      ui.showError(
        null,
        title,
        mxResources.get('help'),
        function () {
          ui.editor.graph.openLink(FilenameDialog.filenameHelpLink);
        },
        null,
        mxResources.get('ok'),
        null,
        null,
        null,
        340,
        90
      );
    }

    mxEvent.consume(evt);
  });

  nameChanged();

  return hint;
};

/**
 *
 */
FilenameDialog.createFileTypes = function (editorUi, nameInput, types) {
  var typeSelect = document.createElement('select');

  for (var i = 0; i < types.length; i++) {
    var typeOption = document.createElement('option');
    typeOption.setAttribute('value', i);
    mxUtils.write(typeOption, mxResources.get(types[i].description) + ' (.' + types[i].extension + ')');
    typeSelect.appendChild(typeOption);
  }

  mxEvent.addListener(typeSelect, 'change', function (evt) {
    var ext = types[typeSelect.value].extension;
    var idx = nameInput.value.lastIndexOf('.');

    if (idx > 0) {
      var ext = types[typeSelect.value].extension;
      nameInput.value = nameInput.value.substring(0, idx + 1) + ext;
    } else {
      nameInput.value = nameInput.value + '.' + ext;
    }

    if ('createEvent' in document) {
      var changeEvent = document.createEvent('HTMLEvents');
      changeEvent.initEvent('change', false, true);
      nameInput.dispatchEvent(changeEvent);
    } else {
      nameInput.fireEvent('onchange');
    }
  });

  var nameInputChanged = function (evt) {
    var idx = nameInput.value.lastIndexOf('.');
    var active = 0;

    // Finds current extension
    if (idx > 0) {
      var ext = nameInput.value.toLowerCase().substring(idx + 1);

      for (var i = 0; i < types.length; i++) {
        if (ext == types[i].extension) {
          active = i;
          break;
        }
      }
    }

    typeSelect.value = active;
  };

  mxEvent.addListener(nameInput, 'change', nameInputChanged);
  mxEvent.addListener(nameInput, 'keyup', nameInputChanged);
  nameInputChanged();

  return typeSelect;
};

/**
 * Static overrides
 */
(function () {
  // Uses HTML for background pages (to support grid background image)
  mxGraphView.prototype.validateBackgroundPage = function () {
    var graph = this.graph;

    if (graph.container != null && !graph.transparentBackground) {
      if (graph.pageVisible) {
        var bounds = this.getBackgroundPageBounds();

        if (this.backgroundPageShape == null) {
          // Finds first element in graph container
          var firstChild = graph.container.firstChild;

          while (firstChild != null && firstChild.nodeType != mxConstants.NODETYPE_ELEMENT) {
            firstChild = firstChild.nextSibling;
          }

          if (firstChild != null) {
            this.backgroundPageShape = this.createBackgroundPageShape(bounds);
            this.backgroundPageShape.scale = 1;

            // Shadow filter causes problems in outline window in quirks mode. IE8 standards
            // also has known rendering issues inside mxWindow but not using shadow is worse.
            this.backgroundPageShape.isShadow = !mxClient.IS_QUIRKS;
            this.backgroundPageShape.dialect = mxConstants.DIALECT_STRICTHTML;
            this.backgroundPageShape.init(graph.container);

            // Required for the browser to render the background page in correct order
            firstChild.style.position = 'absolute';
            graph.container.insertBefore(this.backgroundPageShape.node, firstChild);
            this.backgroundPageShape.redraw();

            this.backgroundPageShape.node.className = 'geBackgroundPage';

            // Adds listener for double click handling on background
            mxEvent.addListener(
              this.backgroundPageShape.node,
              'dblclick',
              mxUtils.bind(this, function (evt) {
                graph.dblClick(evt);
              })
            );

            // Adds basic listeners for graph event dispatching outside of the
            // container and finishing the handling of a single gesture
            mxEvent.addGestureListeners(
              this.backgroundPageShape.node,
              mxUtils.bind(this, function (evt) {
                graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
              }),
              mxUtils.bind(this, function (evt) {
                // Hides the tooltip if mouse is outside container
                if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) {
                  graph.tooltipHandler.hide();
                }

                if (graph.isMouseDown && !mxEvent.isConsumed(evt)) {
                  graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
                }
              }),
              mxUtils.bind(this, function (evt) {
                graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
              })
            );
          }
        } else {
          this.backgroundPageShape.scale = 1;
          this.backgroundPageShape.bounds = bounds;
          this.backgroundPageShape.redraw();
        }
      } else if (this.backgroundPageShape != null) {
        this.backgroundPageShape.destroy();
        this.backgroundPageShape = null;
      }

      this.validateBackgroundStyles();
    }
  };

  // Updates the CSS of the background to draw the grid
  mxGraphView.prototype.validateBackgroundStyles = function () {
    var graph = this.graph;
    var color =
      graph.background == null || graph.background == mxConstants.NONE
        ? graph.defaultPageBackgroundColor
        : graph.background;
    var gridColor = color != null && this.gridColor != color.toLowerCase() ? this.gridColor : '#ffffff';
    var image = 'none';
    var position = '';

    if (graph.isGridEnabled()) {
      var phase = 10;

      if (mxClient.IS_SVG) {
        // Generates the SVG required for drawing the dynamic grid
        image = unescape(encodeURIComponent(this.createSvgGrid(gridColor)));
        image = window.btoa ? btoa(image) : Base64.encode(image, true);
        image = 'url(' + 'data:image/svg+xml;base64,' + image + ')';
        phase = graph.gridSize * this.scale * this.gridSteps;
      } else {
        // Fallback to grid wallpaper with fixed size
        image = 'url(' + this.gridImage + ')';
      }

      var x0 = 0;
      var y0 = 0;

      if (graph.view.backgroundPageShape != null) {
        var bds = this.getBackgroundPageBounds();

        x0 = 1 + bds.x;
        y0 = 1 + bds.y;
      }

      // Computes the offset to maintain origin for grid
      position =
        -Math.round(phase - mxUtils.mod(this.translate.x * this.scale - x0, phase)) +
        'px ' +
        -Math.round(phase - mxUtils.mod(this.translate.y * this.scale - y0, phase)) +
        'px';
    }

    var canvas = graph.view.canvas;

    if (canvas.ownerSVGElement != null) {
      canvas = canvas.ownerSVGElement;
    }

    if (graph.view.backgroundPageShape != null) {
      graph.view.backgroundPageShape.node.style.backgroundPosition = position;
      graph.view.backgroundPageShape.node.style.backgroundImage = image;
      graph.view.backgroundPageShape.node.style.backgroundColor = color;
      graph.container.className = 'geDiagramContainer geDiagramBackdrop';
      canvas.style.backgroundImage = 'none';
      canvas.style.backgroundColor = '';
    } else {
      graph.container.className = 'geDiagramContainer';
      canvas.style.backgroundPosition = position;
      canvas.style.backgroundColor = color;
      canvas.style.backgroundImage = image;
    }
  };

  // Returns the SVG required for painting the background grid.
  mxGraphView.prototype.createSvgGrid = function (color) {
    var tmp = this.graph.gridSize * this.scale;

    while (tmp < this.minGridSize) {
      tmp *= 2;
    }

    var tmp2 = this.gridSteps * tmp;

    // Small grid lines
    var d = [];

    for (var i = 1; i < this.gridSteps; i++) {
      var tmp3 = i * tmp;
      d.push('M 0 ' + tmp3 + ' L ' + tmp2 + ' ' + tmp3 + ' M ' + tmp3 + ' 0 L ' + tmp3 + ' ' + tmp2);
    }

    // KNOWN: Rounding errors for certain scales (eg. 144%, 121% in Chrome, FF and Safari). Workaround
    // in Chrome is to use 100% for the svg size, but this results in blurred grid for large diagrams.
    var size = tmp2;
    var svg =
      '<svg width="' +
      size +
      '" height="' +
      size +
      '" xmlns="' +
      mxConstants.NS_SVG +
      '">' +
      '<defs><pattern id="grid" width="' +
      tmp2 +
      '" height="' +
      tmp2 +
      '" patternUnits="userSpaceOnUse">' +
      '<path d="' +
      d.join(' ') +
      '" fill="none" stroke="' +
      color +
      '" opacity="0.2" stroke-width="1"/>' +
      '<path d="M ' +
      tmp2 +
      ' 0 L 0 0 0 ' +
      tmp2 +
      '" fill="none" stroke="' +
      color +
      '" stroke-width="1"/>' +
      '</pattern></defs><rect width="100%" height="100%" fill="url(#grid)"/></svg>';

    return svg;
  };

  // Adds panning for the grid with no page view and disabled scrollbars
  var mxGraphPanGraph = mxGraph.prototype.panGraph;
  mxGraph.prototype.panGraph = function (dx, dy) {
    mxGraphPanGraph.apply(this, arguments);

    if (this.shiftPreview1 != null) {
      var canvas = this.view.canvas;

      if (canvas.ownerSVGElement != null) {
        canvas = canvas.ownerSVGElement;
      }

      var phase = this.gridSize * this.view.scale * this.view.gridSteps;
      var position =
        -Math.round(phase - mxUtils.mod(this.view.translate.x * this.view.scale + dx, phase)) +
        'px ' +
        -Math.round(phase - mxUtils.mod(this.view.translate.y * this.view.scale + dy, phase)) +
        'px';
      canvas.style.backgroundPosition = position;
    }
  };

  // Draws page breaks only within the page
  mxGraph.prototype.updatePageBreaks = function (visible, width, height) {
    var scale = this.view.scale;
    var tr = this.view.translate;
    var fmt = this.pageFormat;
    var ps = scale * this.pageScale;

    var bounds2 = this.view.getBackgroundPageBounds();

    width = bounds2.width;
    height = bounds2.height;
    var bounds = new mxRectangle(scale * tr.x, scale * tr.y, fmt.width * ps, fmt.height * ps);

    // Does not show page breaks if the scale is too small
    visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;

    var horizontalCount = visible ? Math.ceil(height / bounds.height) - 1 : 0;
    var verticalCount = visible ? Math.ceil(width / bounds.width) - 1 : 0;
    var right = bounds2.x + width;
    var bottom = bounds2.y + height;

    if (this.horizontalPageBreaks == null && horizontalCount > 0) {
      this.horizontalPageBreaks = [];
    }

    if (this.verticalPageBreaks == null && verticalCount > 0) {
      this.verticalPageBreaks = [];
    }

    var drawPageBreaks = mxUtils.bind(this, function (breaks) {
      if (breaks != null) {
        var count = breaks == this.horizontalPageBreaks ? horizontalCount : verticalCount;

        for (var i = 0; i <= count; i++) {
          var pts =
            breaks == this.horizontalPageBreaks
              ? [
                  new mxPoint(Math.round(bounds2.x), Math.round(bounds2.y + (i + 1) * bounds.height)),
                  new mxPoint(Math.round(right), Math.round(bounds2.y + (i + 1) * bounds.height)),
                ]
              : [
                  new mxPoint(Math.round(bounds2.x + (i + 1) * bounds.width), Math.round(bounds2.y)),
                  new mxPoint(Math.round(bounds2.x + (i + 1) * bounds.width), Math.round(bottom)),
                ];

          if (breaks[i] != null) {
            breaks[i].points = pts;
            breaks[i].redraw();
          } else {
            var pageBreak = new mxPolyline(pts, this.pageBreakColor);
            pageBreak.dialect = this.dialect;
            pageBreak.isDashed = this.pageBreakDashed;
            pageBreak.pointerEvents = false;
            pageBreak.init(this.view.backgroundPane);
            pageBreak.redraw();

            breaks[i] = pageBreak;
          }
        }

        for (var i = count; i < breaks.length; i++) {
          breaks[i].destroy();
        }

        breaks.splice(count, breaks.length - count);
      }
    });

    drawPageBreaks(this.horizontalPageBreaks);
    drawPageBreaks(this.verticalPageBreaks);
  };

  // Disables removing relative children and table rows and cells from parents
  var mxGraphHandlerShouldRemoveCellsFromParent = mxGraphHandler.prototype.shouldRemoveCellsFromParent;
  mxGraphHandler.prototype.shouldRemoveCellsFromParent = function (parent, cells, evt) {
    for (var i = 0; i < cells.length; i++) {
      if (this.graph.isTableCell(cells[i]) || this.graph.isTableRow(cells[i])) {
        return false;
      } else if (this.graph.getModel().isVertex(cells[i])) {
        var geo = this.graph.getCellGeometry(cells[i]);

        if (geo != null && geo.relative) {
          return false;
        }
      }
    }

    return mxGraphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
  };

  // Overrides to ignore hotspot only for target terminal
  var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
  mxConnectionHandler.prototype.createMarker = function () {
    var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);

    marker.intersects = mxUtils.bind(this, function (state, evt) {
      if (this.isConnecting()) {
        return true;
      }

      return mxCellMarker.prototype.intersects.apply(marker, arguments);
    });

    return marker;
  };

  // Creates background page shape
  mxGraphView.prototype.createBackgroundPageShape = function (bounds) {
    return new mxRectangleShape(bounds, '#ffffff', this.graph.defaultPageBorderColor);
  };

  // Fits the number of background pages to the graph
  mxGraphView.prototype.getBackgroundPageBounds = function () {
    var gb = this.getGraphBounds();

    // Computes unscaled, untranslated graph bounds
    var x = gb.width > 0 ? gb.x / this.scale - this.translate.x : 0;
    var y = gb.height > 0 ? gb.y / this.scale - this.translate.y : 0;
    var w = gb.width / this.scale;
    var h = gb.height / this.scale;

    var fmt = this.graph.pageFormat;
    var ps = this.graph.pageScale;

    var pw = fmt.width * ps;
    var ph = fmt.height * ps;

    var x0 = Math.floor(Math.min(0, x) / pw);
    var y0 = Math.floor(Math.min(0, y) / ph);
    var xe = Math.ceil(Math.max(1, x + w) / pw);
    var ye = Math.ceil(Math.max(1, y + h) / ph);

    var rows = xe - x0;
    var cols = ye - y0;

    var bounds = new mxRectangle(
      this.scale * (this.translate.x + x0 * pw),
      this.scale * (this.translate.y + y0 * ph),
      this.scale * rows * pw,
      this.scale * cols * ph
    );

    return bounds;
  };

  // Add panning for background page in VML
  var graphPanGraph = mxGraph.prototype.panGraph;
  mxGraph.prototype.panGraph = function (dx, dy) {
    graphPanGraph.apply(this, arguments);

    if (
      this.dialect != mxConstants.DIALECT_SVG &&
      this.view.backgroundPageShape != null &&
      (!this.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.container))
    ) {
      this.view.backgroundPageShape.node.style.marginLeft = dx + 'px';
      this.view.backgroundPageShape.node.style.marginTop = dy + 'px';
    }
  };

  /**
   * Consumes click events for disabled menu items.
   */
  var mxPopupMenuAddItem = mxPopupMenu.prototype.addItem;
  mxPopupMenu.prototype.addItem = function (title, image, funct, parent, iconCls, enabled) {
    var result = mxPopupMenuAddItem.apply(this, arguments);

    if (enabled != null && !enabled) {
      mxEvent.addListener(result, 'mousedown', function (evt) {
        mxEvent.consume(evt);
      });
    }

    return result;
  };

  /**
   * Selects tables before cells and rows.
   */
  var mxGraphHandlerIsPropagateSelectionCell = mxGraphHandler.prototype.isPropagateSelectionCell;
  mxGraphHandler.prototype.isPropagateSelectionCell = function (cell, immediate, me) {
    var result = false;
    var parent = this.graph.model.getParent(cell);

    if (immediate) {
      var geo = this.graph.model.isEdge(cell) ? null : this.graph.getCellGeometry(cell);

      result =
        !this.graph.model.isEdge(parent) &&
        !this.graph.isSiblingSelected(cell) &&
        ((geo != null && geo.relative) || !this.graph.isContainer(parent) || this.graph.isPart(cell));
    } else {
      result = mxGraphHandlerIsPropagateSelectionCell.apply(this, arguments);

      if (this.graph.isTableCell(cell) || this.graph.isTableRow(cell)) {
        var table = parent;

        if (!this.graph.isTable(table)) {
          table = this.graph.model.getParent(table);
        }

        result =
          !this.graph.selectionCellsHandler.isHandled(table) ||
          (this.graph.isCellSelected(table) && this.graph.isToggleEvent(me.getEvent())) ||
          (this.graph.isCellSelected(cell) && !this.graph.isToggleEvent(me.getEvent())) ||
          (this.graph.isTableCell(cell) && this.graph.isCellSelected(parent));
      }
    }

    return result;
  };

  /**
   * Returns last selected ancestor
   */
  mxPopupMenuHandler.prototype.getCellForPopupEvent = function (me) {
    var cell = me.getCell();
    var model = this.graph.getModel();
    var parent = model.getParent(cell);
    var state = this.graph.view.getState(parent);
    var selected = this.graph.isCellSelected(cell);

    while (state != null && (model.isVertex(parent) || model.isEdge(parent))) {
      var temp = this.graph.isCellSelected(parent);
      selected = selected || temp;

      if (temp || (!selected && (this.graph.isTableCell(cell) || this.graph.isTableRow(cell)))) {
        cell = parent;
      }

      parent = model.getParent(parent);
    }

    return cell;
  };
})();
/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
/**
 * Construcs a new sidebar for the given editor.
 */
function Sidebar(editorUi, container) {
  this.editorUi = editorUi;
  this.container = container;
  this.palettes = new Object();
  this.taglist = new Object();
  this.showTooltips = true;
  this.graph = editorUi.createTemporaryGraph(this.editorUi.editor.graph.getStylesheet());
  this.graph.cellRenderer.minSvgStrokeWidth = this.minThumbStrokeWidth;
  this.graph.cellRenderer.antiAlias = this.thumbAntiAlias;
  this.graph.container.style.visibility = 'hidden';
  this.graph.foldingEnabled = false;

  document.body.appendChild(this.graph.container);

  this.pointerUpHandler = mxUtils.bind(this, function () {
    this.showTooltips = true;
  });

  mxEvent.addListener(document, mxClient.IS_POINTER ? 'pointerup' : 'mouseup', this.pointerUpHandler);

  this.pointerDownHandler = mxUtils.bind(this, function () {
    this.showTooltips = false;
    this.hideTooltip();
  });

  mxEvent.addListener(document, mxClient.IS_POINTER ? 'pointerdown' : 'mousedown', this.pointerDownHandler);

  this.pointerMoveHandler = mxUtils.bind(this, function (evt) {
    var src = mxEvent.getSource(evt);

    while (src != null) {
      if (src == this.currentElt) {
        return;
      }

      src = src.parentNode;
    }

    this.hideTooltip();
  });

  mxEvent.addListener(document, mxClient.IS_POINTER ? 'pointermove' : 'mousemove', this.pointerMoveHandler);

  // Handles mouse leaving the window
  this.pointerOutHandler = mxUtils.bind(this, function (evt) {
    if (evt.toElement == null && evt.relatedTarget == null) {
      this.hideTooltip();
    }
  });

  mxEvent.addListener(document, mxClient.IS_POINTER ? 'pointerout' : 'mouseout', this.pointerOutHandler);

  // Enables tooltips after scroll
  mxEvent.addListener(
    container,
    'scroll',
    mxUtils.bind(this, function () {
      this.showTooltips = true;
      this.hideTooltip();
    })
  );

  this.init();
}

/**
 * Adds all palettes to the sidebar.
 */
Sidebar.prototype.init = function () {
  var dir = STENCIL_PATH;

  this.addSearchPalette(true);
  this.addGeneralPalette(true);
  this.addMiscPalette(false);
  this.addAdvancedPalette(false);
  this.addBasicPalette(dir);

  this.setCurrentSearchEntryLibrary('atlassian');
  this.addStencilPalette(
    'atlassian',
    mxResources.get('atlassian'),
    dir + '/atlassian.xml',
    ';whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#000000;strokeWidth=2'
  );
  this.setCurrentSearchEntryLibrary();

  this.setCurrentSearchEntryLibrary('citrix');
  this.addStencilPalette(
    'citrix',
    mxResources.get('citrix'),
    dir + '/citrix.xml',
    ';whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#000000;strokeWidth=2'
  );
  this.setCurrentSearchEntryLibrary();

  this.setCurrentSearchEntryLibrary('arrows');
  this.addStencilPalette(
    'arrows',
    mxResources.get('arrows'),
    dir + '/arrows.xml',
    ';whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#000000;strokeWidth=2'
  );
  this.setCurrentSearchEntryLibrary();

  this.addUmlPalette(false);
  this.addBpmnPalette(dir, false);

  this.setCurrentSearchEntryLibrary('flowchart');
  this.addStencilPalette(
    'flowchart',
    'Flowchart',
    dir + '/flowchart.xml',
    ';whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#000000;strokeWidth=2'
  );
  this.setCurrentSearchEntryLibrary();

  // this.setCurrentSearchEntryLibrary('clipart');
  // this.addImagePalette(
  //   'clipart',
  //   mxResources.get('clipart'),
  //   dir + '/clipart/',
  //   '_128x128.png',
  //   [
  //     'Earth_globe',
  //     'Empty_Folder',
  //     'Full_Folder',
  //     'Gear',
  //     'Lock',
  //     'Software',
  //     'Virus',
  //     'Email',
  //     'Database',
  //     'Router_Icon',
  //     'iPad',
  //     'iMac',
  //     'Laptop',
  //     'MacBook',
  //     'Monitor_Tower',
  //     'Printer',
  //     'Server_Tower',
  //     'Workstation',
  //     'Firewall_02',
  //     'Wireless_Router_N',
  //     'Credit_Card',
  //     'Piggy_Bank',
  //     'Graph',
  //     'Safe',
  //     'Shopping_Cart',
  //     'Suit1',
  //     'Suit2',
  //     'Suit3',
  //     'Pilot1',
  //     'Worker1',
  //     'Soldier1',
  //     'Doctor1',
  //     'Tech1',
  //     'Security1',
  //     'Telesales1',
  //   ],
  //   null,
  //   { Wireless_Router_N: 'wireless router switch wap wifi access point wlan', Router_Icon: 'router switch' }
  // );
  // this.setCurrentSearchEntryLibrary();
};

/**
 * Sets the default font size.
 */
Sidebar.prototype.collapsedImage = !mxClient.IS_SVG
  ? IMAGE_PATH + '/collapsed.gif'
  : 'data:image/gif;base64,R0lGODlhDQANAIABAJmZmf///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozNUQyRTJFNjZGNUYxMUU1QjZEOThCNDYxMDQ2MzNCQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozNUQyRTJFNzZGNUYxMUU1QjZEOThCNDYxMDQ2MzNCQiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjFERjc3MEUxNkY1RjExRTVCNkQ5OEI0NjEwNDYzM0JCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjFERjc3MEUyNkY1RjExRTVCNkQ5OEI0NjEwNDYzM0JCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkEAQAAAQAsAAAAAA0ADQAAAhSMj6lrwAjcC1GyahV+dcZJgeIIFgA7';

/**
 * Sets the default font size.
 */
Sidebar.prototype.expandedImage = !mxClient.IS_SVG
  ? IMAGE_PATH + '/expanded.gif'
  : 'data:image/gif;base64,R0lGODlhDQANAIABAJmZmf///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxREY3NzBERjZGNUYxMUU1QjZEOThCNDYxMDQ2MzNCQiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxREY3NzBFMDZGNUYxMUU1QjZEOThCNDYxMDQ2MzNCQiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjFERjc3MERENkY1RjExRTVCNkQ5OEI0NjEwNDYzM0JCIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjFERjc3MERFNkY1RjExRTVCNkQ5OEI0NjEwNDYzM0JCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkEAQAAAQAsAAAAAA0ADQAAAhGMj6nL3QAjVHIu6azbvPtWAAA7';

/**
 *
 */
Sidebar.prototype.searchImage = !mxClient.IS_SVG
  ? IMAGE_PATH + '/search.png'
  : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAEaSURBVHjabNGxS5VxFIfxz71XaWuQUJCG/gCHhgTD9VpEETg4aMOlQRp0EoezObgcd220KQiXmpretTAHQRBdojlQEJyukPdt+b1ywfvAGc7wnHP4nlZd1yKijQW8xzNc4Su+ZOYfQ3T6/f4YNvEJYzjELXp4VVXVz263+7cR2niBxAFeZ2YPi3iHR/gYERPDwhpOsd6sz8x/mfkNG3iOlWFhFj8y89J9KvzGXER0GuEaD42mgwHqUtoljbcRsTBCeINpfM/MgZLKPpaxFxGbOCqDXmILN7hoJrTKH+axhxmcYRxP0MIDnOBDZv5q1XUNIuJxifJp+UNV7t7BFM6xeic0RMQ4Bpl5W/ol7GISx/eEUUTECrbx+f8A8xhiZht9zsgAAAAASUVORK5CYII=';

/**
 *
 */
Sidebar.prototype.dragPreviewBorder = '1px dashed black';

/**
 * Specifies if tooltips should be visible. Default is true.
 */
Sidebar.prototype.enableTooltips = true;

/**
 * Specifies the delay for the tooltip. Default is 16 px.
 */
Sidebar.prototype.tooltipBorder = 16;

/**
 * Specifies the delay for the tooltip. Default is 300 ms.
 */
Sidebar.prototype.tooltipDelay = 300;

/**
 * Specifies the delay for the drop target icons. Default is 200 ms.
 */
Sidebar.prototype.dropTargetDelay = 200;

/**
 * Specifies the URL of the gear image.
 */
Sidebar.prototype.gearImage = STENCIL_PATH + '/clipart/Gear_128x128.png';

/**
 * Specifies the width of the thumbnails.
 */
Sidebar.prototype.thumbWidth = 42;

/**
 * Specifies the height of the thumbnails.
 */
Sidebar.prototype.thumbHeight = 42;

/**
 * Specifies the width of the thumbnails.
 */
Sidebar.prototype.minThumbStrokeWidth = 1;

/**
 * Specifies the width of the thumbnails.
 */
Sidebar.prototype.thumbAntiAlias = false;

/**
 * Specifies the padding for the thumbnails. Default is 3.
 */
Sidebar.prototype.thumbPadding = document.documentMode >= 5 ? 2 : 3;

/**
 * Specifies the delay for the tooltip. Default is 2 px.
 */
Sidebar.prototype.thumbBorder = 2;

/*
 * Experimental smaller sidebar entries
 */
if (urlParams['sidebar-entries'] != 'large') {
  Sidebar.prototype.thumbPadding = document.documentMode >= 5 ? 0 : 1;
  Sidebar.prototype.thumbBorder = 1;
  Sidebar.prototype.thumbWidth = 32;
  Sidebar.prototype.thumbHeight = 30;
  Sidebar.prototype.minThumbStrokeWidth = 1.3;
  Sidebar.prototype.thumbAntiAlias = true;
}

/**
 * Specifies the size of the sidebar titles.
 */
Sidebar.prototype.sidebarTitleSize = 9;

/**
 * Specifies if titles in the sidebar should be enabled.
 */
Sidebar.prototype.sidebarTitles = false;

/**
 * Specifies if titles in the tooltips should be enabled.
 */
Sidebar.prototype.tooltipTitles = true;

/**
 * Specifies if titles in the tooltips should be enabled.
 */
Sidebar.prototype.maxTooltipWidth = 400;

/**
 * Specifies if titles in the tooltips should be enabled.
 */
Sidebar.prototype.maxTooltipHeight = 400;

/**
 * Specifies if stencil files should be loaded and added to the search index
 * when stencil palettes are added. If this is false then the stencil files
 * are lazy-loaded when the palette is shown.
 */
Sidebar.prototype.addStencilsToIndex = true;

/**
 * Specifies the width for clipart images. Default is 80.
 */
Sidebar.prototype.defaultImageWidth = 80;

/**
 * Specifies the height for clipart images. Default is 80.
 */
Sidebar.prototype.defaultImageHeight = 80;

/**
 * Adds all palettes to the sidebar.
 */
Sidebar.prototype.getTooltipOffset = function () {
  return new mxPoint(0, 0);
};

/**
 * Adds all palettes to the sidebar.
 */
Sidebar.prototype.showTooltip = function (elt, cells, w, h, title, showLabel) {
  if (this.enableTooltips && this.showTooltips) {
    if (this.currentElt != elt) {
      if (this.thread != null) {
        window.clearTimeout(this.thread);
        this.thread = null;
      }

      var show = mxUtils.bind(this, function () {
        // Lazy creation of the DOM nodes and graph instance
        if (this.tooltip == null) {
          this.tooltip = document.createElement('div');
          this.tooltip.className = 'geSidebarTooltip';
          this.tooltip.style.zIndex = mxPopupMenu.prototype.zIndex - 1;
          document.body.appendChild(this.tooltip);

          this.graph2 = new Graph(this.tooltip, null, null, this.editorUi.editor.graph.getStylesheet());
          this.graph2.resetViewOnRootChange = false;
          this.graph2.foldingEnabled = false;
          this.graph2.gridEnabled = false;
          this.graph2.autoScroll = false;
          this.graph2.setTooltips(false);
          this.graph2.setConnectable(false);
          this.graph2.setEnabled(false);

          if (!mxClient.IS_SVG) {
            this.graph2.view.canvas.style.position = 'relative';
          }
        }

        this.graph2.model.clear();
        this.graph2.view.setTranslate(this.tooltipBorder, this.tooltipBorder);

        if (w > this.maxTooltipWidth || h > this.maxTooltipHeight) {
          this.graph2.view.scale =
            Math.round(Math.min(this.maxTooltipWidth / w, this.maxTooltipHeight / h) * 100) / 100;
        } else {
          this.graph2.view.scale = 1;
        }

        this.tooltip.style.display = 'block';
        this.graph2.labelsVisible = showLabel == null || showLabel;
        var fo = mxClient.NO_FO;
        mxClient.NO_FO = Editor.prototype.originalNoForeignObject;

        // Applies current style for preview
        var temp = this.graph2.cloneCells(cells);
        this.editorUi.insertHandler(temp, null, this.graph2.model);
        this.graph2.addCells(temp);

        mxClient.NO_FO = fo;

        var bounds = this.graph2.getGraphBounds();
        var width = bounds.width + 2 * this.tooltipBorder + 4;
        var height = bounds.height + 2 * this.tooltipBorder;

        if (mxClient.IS_QUIRKS) {
          height += 4;
          this.tooltip.style.overflow = 'hidden';
        } else {
          this.tooltip.style.overflow = 'visible';
        }

        this.tooltip.style.width = width + 'px';
        var w2 = width;

        // Adds title for entry
        if (this.tooltipTitles && title != null && title.length > 0) {
          if (this.tooltipTitle == null) {
            this.tooltipTitle = document.createElement('div');
            this.tooltipTitle.style.borderTop = '1px solid gray';
            this.tooltipTitle.style.textAlign = 'center';
            this.tooltipTitle.style.width = '100%';
            this.tooltipTitle.style.overflow = 'hidden';
            this.tooltipTitle.style.position = 'absolute';
            this.tooltipTitle.style.paddingTop = '6px';
            this.tooltipTitle.style.bottom = '6px';

            this.tooltip.appendChild(this.tooltipTitle);
          } else {
            this.tooltipTitle.innerHTML = '';
          }

          this.tooltipTitle.style.display = '';
          mxUtils.write(this.tooltipTitle, title);

          // Allows for wider labels
          w2 = Math.min(this.maxTooltipWidth, Math.max(width, this.tooltipTitle.scrollWidth + 4));
          var ddy = this.tooltipTitle.offsetHeight + 10;
          height += ddy;

          if (mxClient.IS_SVG) {
            this.tooltipTitle.style.marginTop = 2 - ddy + 'px';
          } else {
            height -= 6;
            this.tooltipTitle.style.top = height - ddy + 'px';
          }
        } else if (this.tooltipTitle != null && this.tooltipTitle.parentNode != null) {
          this.tooltipTitle.style.display = 'none';
        }

        // Updates width if label is wider
        if (w2 > width) {
          this.tooltip.style.width = w2 + 'px';
        }

        this.tooltip.style.height = height + 'px';
        var x0 = -Math.round(bounds.x - this.tooltipBorder) + (w2 > width ? (w2 - width) / 2 : 0);
        var y0 = -Math.round(bounds.y - this.tooltipBorder);

        var b = document.body;
        var d = document.documentElement;
        var off = this.getTooltipOffset();
        var bottom = Math.max(b.clientHeight || 0, d.clientHeight);
        var left =
          this.container.clientWidth + this.editorUi.splitSize + 3 + this.editorUi.container.offsetLeft + off.x;
        var top =
          Math.min(
            bottom - height - 20 /*status bar*/,
            Math.max(
              0,
              this.editorUi.container.offsetTop +
                this.container.offsetTop +
                elt.offsetTop -
                this.container.scrollTop -
                height / 2 +
                16
            )
          ) + off.y;

        if (mxClient.IS_SVG) {
          if (x0 != 0 || y0 != 0) {
            this.graph2.view.canvas.setAttribute('transform', 'translate(' + x0 + ',' + y0 + ')');
          } else {
            this.graph2.view.canvas.removeAttribute('transform');
          }
        } else {
          this.graph2.view.drawPane.style.left = x0 + 'px';
          this.graph2.view.drawPane.style.top = y0 + 'px';
        }

        // Workaround for ignored position CSS style in IE9
        // (changes to relative without the following line)
        this.tooltip.style.position = 'absolute';
        this.tooltip.style.left = left + 'px';
        this.tooltip.style.top = top + 'px';
      });

      if (this.tooltip != null && this.tooltip.style.display != 'none') {
        show();
      } else {
        this.thread = window.setTimeout(show, this.tooltipDelay);
      }

      this.currentElt = elt;
    }
  }
};

/**
 * Hides the current tooltip.
 */
Sidebar.prototype.hideTooltip = function () {
  if (this.thread != null) {
    window.clearTimeout(this.thread);
    this.thread = null;
  }

  if (this.tooltip != null) {
    this.tooltip.style.display = 'none';
    this.currentElt = null;
  }
};

/**
 * Hides the current tooltip.
 */
Sidebar.prototype.addDataEntry = function (tags, width, height, title, data) {
  return this.addEntry(
    tags,
    mxUtils.bind(this, function () {
      return this.createVertexTemplateFromData(data, width, height, title);
    })
  );
};

/**
 * Adds the give entries to the search index.
 */
Sidebar.prototype.addEntries = function (images) {
  for (var i = 0; i < images.length; i++) {
    mxUtils.bind(this, function (img) {
      var data = img.data;
      var tags = img.title != null ? img.title : '';

      if (img.tags != null) {
        tags += ' ' + img.tags;
      }

      if (data != null && tags.length > 0) {
        this.addEntry(
          tags,
          mxUtils.bind(this, function () {
            data = this.editorUi.convertDataUri(data);
            var s = 'shape=image;verticalLabelPosition=bottom;verticalAlign=top;imageAspect=0;';

            if (img.aspect == 'fixed') {
              s += 'aspect=fixed;';
            }

            return this.createVertexTemplate(
              s + 'image=' + data,
              img.w,
              img.h,
              '',
              img.title || '',
              false,
              false,
              true
            );
          })
        );
      } else if (img.xml != null && tags.length > 0) {
        this.addEntry(
          tags,
          mxUtils.bind(this, function () {
            var cells = this.editorUi.stringToCells(Graph.decompress(img.xml));

            return this.createVertexTemplateFromCells(cells, img.w, img.h, img.title || '', true, false, true);
          })
        );
      }
    })(images[i]);
  }
};

/**
 * Hides the current tooltip.
 */
Sidebar.prototype.setCurrentSearchEntryLibrary = function (id, lib) {
  this.currentSearchEntryLibrary = id != null ? { id: id, lib: lib } : null;
};

/**
 * Hides the current tooltip.
 */
Sidebar.prototype.addEntry = function (tags, fn) {
  if (this.taglist != null && tags != null && tags.length > 0) {
    if (this.currentSearchEntryLibrary != null) {
      fn.parentLibraries = [this.currentSearchEntryLibrary];
    }

    // Replaces special characters
    var tmp = tags
      .toLowerCase()
      .replace(/[\/\,\(\)]/g, ' ')
      .split(' ');
    var tagList = [];
    var hash = {};

    // Finds unique tags
    for (var i = 0; i < tmp.length; i++) {
      if (hash[tmp[i]] == null) {
        hash[tmp[i]] = true;
        tagList.push(tmp[i]);
      }

      // Adds additional entry with removed trailing numbers
      var normalized = tmp[i].replace(/\.*\d*$/, '');

      if (normalized != tmp[i]) {
        if (hash[normalized] == null) {
          hash[normalized] = true;
          tagList.push(normalized);
        }
      }
    }

    for (var i = 0; i < tagList.length; i++) {
      this.addEntryForTag(tagList[i], fn);
    }
  }

  return fn;
};

/**
 * Hides the current tooltip.
 */
Sidebar.prototype.addEntryForTag = function (tag, fn) {
  if (tag != null && tag.length > 1) {
    var entry = this.taglist[tag];

    if (typeof entry !== 'object') {
      entry = { entries: [] };
      this.taglist[tag] = entry;
    }

    entry.entries.push(fn);
  }
};

/**
 * Adds shape search UI.
 */
Sidebar.prototype.searchEntries = function (searchTerms, count, page, success, error) {
  if (this.taglist != null && searchTerms != null) {
    var tmp = searchTerms.toLowerCase().split(' ');
    var dict = new mxDictionary();
    var max = (page + 1) * count;
    var results = [];
    var index = 0;

    for (var i = 0; i < tmp.length; i++) {
      if (tmp[i].length > 0) {
        var entry = this.taglist[tmp[i]];
        var tmpDict = new mxDictionary();

        if (entry != null) {
          var arr = entry.entries;
          results = [];

          for (var j = 0; j < arr.length; j++) {
            var entry = arr[j];

            // NOTE Array does not contain duplicates
            if ((index == 0) == (dict.get(entry) == null)) {
              tmpDict.put(entry, entry);
              results.push(entry);

              if (i == tmp.length - 1 && results.length == max) {
                success(results.slice(page * count, max), max, true, tmp);

                return;
              }
            }
          }
        } else {
          results = [];
        }

        dict = tmpDict;
        index++;
      }
    }

    var len = results.length;
    success(results.slice(page * count, (page + 1) * count), len, false, tmp);
  } else {
    success([], null, null, tmp);
  }
};

/**
 * Adds shape search UI.
 */
Sidebar.prototype.filterTags = function (tags) {
  if (tags != null) {
    var arr = tags.split(' ');
    var result = [];
    var hash = {};

    // Ignores tags with leading numbers, strips trailing numbers
    for (var i = 0; i < arr.length; i++) {
      // Removes duplicates
      if (hash[arr[i]] == null) {
        hash[arr[i]] = '1';
        result.push(arr[i]);
      }
    }

    return result.join(' ');
  }

  return null;
};

/**
 * Adds the general palette to the sidebar.
 */
Sidebar.prototype.cloneCell = function (cell, value) {
  var clone = cell.clone();

  if (value != null) {
    clone.value = value;
  }

  return clone;
};

/**
 * Adds shape search UI.
 */
Sidebar.prototype.showPopupMenuForEntry = function (elt, libs, evt) {
  // Hook for subclassers
};

/**
 * Adds shape search UI.
 */
Sidebar.prototype.addSearchPalette = function (expand) {
  var elt = document.createElement('div');
  elt.style.visibility = 'hidden';
  this.container.appendChild(elt);

  var div = document.createElement('div');
  div.className = 'geSidebar';
  div.style.boxSizing = 'border-box';
  div.style.overflow = 'hidden';
  div.style.width = '100%';
  div.style.padding = '8px';
  div.style.paddingTop = '14px';
  div.style.paddingBottom = '0px';

  if (!expand) {
    div.style.display = 'none';
  }

  var inner = document.createElement('div');
  inner.style.whiteSpace = 'nowrap';
  inner.style.textOverflow = 'clip';
  inner.style.paddingBottom = '8px';
  inner.style.cursor = 'default';

  var input = document.createElement('input');
  input.setAttribute('placeholder', mxResources.get('searchShapes'));
  input.setAttribute('type', 'text');
  input.style.fontSize = '12px';
  input.style.overflow = 'hidden';
  input.style.boxSizing = 'border-box';
  input.style.border = 'solid 1px #d5d5d5';
  input.style.borderRadius = '4px';
  input.style.width = '100%';
  input.style.outline = 'none';
  input.style.padding = '6px';
  input.style.paddingRight = '20px';
  inner.appendChild(input);

  var cross = document.createElement('img');
  cross.setAttribute('src', Sidebar.prototype.searchImage);
  cross.setAttribute('title', mxResources.get('search'));
  cross.style.position = 'relative';
  cross.style.left = '-18px';

  if (mxClient.IS_QUIRKS) {
    input.style.height = '28px';
    cross.style.top = '-4px';
  } else {
    cross.style.top = '1px';
  }

  // Needed to block event transparency in IE
  cross.style.background = "url('" + this.editorUi.editor.transparentImage + "')";

  var find;

  inner.appendChild(cross);
  div.appendChild(inner);

  var center = document.createElement('center');
  var button = mxUtils.button(mxResources.get('moreResults'), function () {
    find();
  });
  button.style.display = 'none';

  // Workaround for inherited line-height in quirks mode
  button.style.lineHeight = 'normal';
  button.style.fontSize = '12px';
  button.style.padding = '6px 12px 6px 12px';
  button.style.marginTop = '4px';
  button.style.marginBottom = '8px';
  center.style.paddingTop = '4px';
  center.style.paddingBottom = '4px';

  center.appendChild(button);
  div.appendChild(center);

  var searchTerm = '';
  var active = false;
  var complete = false;
  var page = 0;
  var hash = new Object();

  // Count is dynamically updated below
  var count = 12;

  var clearDiv = mxUtils.bind(this, function () {
    active = false;
    this.currentSearch = null;
    var child = div.firstChild;

    while (child != null) {
      var next = child.nextSibling;

      if (child != inner && child != center) {
        child.parentNode.removeChild(child);
      }

      child = next;
    }
  });

  mxEvent.addListener(cross, 'click', function () {
    if (cross.getAttribute('src') == Dialog.prototype.closeImage) {
      cross.setAttribute('src', Sidebar.prototype.searchImage);
      cross.setAttribute('title', mxResources.get('search'));
      button.style.display = 'none';
      input.value = '';
      searchTerm = '';
      clearDiv();
    }

    input.focus();
  });

  find = mxUtils.bind(this, function () {
    // Shows 4 rows (minimum 4 results)
    count = 4 * Math.max(1, Math.floor(this.container.clientWidth / (this.thumbWidth + 10)));
    this.hideTooltip();

    if (input.value != '') {
      if (center.parentNode != null) {
        if (searchTerm != input.value) {
          clearDiv();
          searchTerm = input.value;
          hash = new Object();
          complete = false;
          page = 0;
        }

        if (!active && !complete) {
          button.setAttribute('disabled', 'true');
          button.style.display = '';
          button.style.cursor = 'wait';
          button.innerHTML = mxResources.get('loading') + '...';
          active = true;

          // Ignores old results
          var current = new Object();
          this.currentSearch = current;

          this.searchEntries(
            searchTerm,
            count,
            page,
            mxUtils.bind(this, function (results, len, more, terms) {
              if (this.currentSearch == current) {
                results = results != null ? results : [];
                active = false;
                page++;
                this.insertSearchHint(div, searchTerm, count, page, results, len, more, terms);

                // Allows to repeat the search
                if (results.length == 0 && page == 1) {
                  searchTerm = '';
                }

                if (center.parentNode != null) {
                  center.parentNode.removeChild(center);
                }

                for (var i = 0; i < results.length; i++) {
                  mxUtils.bind(this, function (result) {
                    try {
                      var elt = result();

                      // Avoids duplicates in results
                      if (hash[elt.innerHTML] == null) {
                        hash[elt.innerHTML] = result.parentLibraries != null ? result.parentLibraries.slice() : [];
                        div.appendChild(elt);
                      } else if (result.parentLibraries != null) {
                        hash[elt.innerHTML] = hash[elt.innerHTML].concat(result.parentLibraries);
                      }

                      mxEvent.addGestureListeners(
                        elt,
                        null,
                        null,
                        mxUtils.bind(this, function (evt) {
                          var libs = hash[elt.innerHTML];

                          if (mxEvent.isPopupTrigger(evt)) {
                            this.showPopupMenuForEntry(elt, libs, evt);
                          }
                        })
                      );

                      // Disables the built-in context menu
                      mxEvent.disableContextMenu(elt);
                    } catch (e) {
                      // ignore
                    }
                  })(results[i]);
                }

                if (more) {
                  button.removeAttribute('disabled');
                  button.innerHTML = mxResources.get('moreResults');
                } else {
                  button.innerHTML = mxResources.get('reset');
                  button.style.display = 'none';
                  complete = true;
                }

                button.style.cursor = '';
                div.appendChild(center);
              }
            }),
            mxUtils.bind(this, function () {
              // TODO: Error handling
              button.style.cursor = '';
            })
          );
        }
      }
    } else {
      clearDiv();
      input.value = '';
      searchTerm = '';
      hash = new Object();
      button.style.display = 'none';
      complete = false;
      input.focus();
    }
  });

  mxEvent.addListener(
    input,
    'keydown',
    mxUtils.bind(this, function (evt) {
      if (evt.keyCode == 13 /* Enter */) {
        find();
        mxEvent.consume(evt);
      }
    })
  );

  mxEvent.addListener(
    input,
    'keyup',
    mxUtils.bind(this, function (evt) {
      if (input.value == '') {
        cross.setAttribute('src', Sidebar.prototype.searchImage);
        cross.setAttribute('title', mxResources.get('search'));
      } else {
        cross.setAttribute('src', Dialog.prototype.closeImage);
        cross.setAttribute('title', mxResources.get('reset'));
      }

      if (input.value == '') {
        complete = true;
        button.style.display = 'none';
      } else if (input.value != searchTerm) {
        button.style.display = 'none';
        complete = false;
      } else if (!active) {
        if (complete) {
          button.style.display = 'none';
        } else {
          button.style.display = '';
        }
      }
    })
  );

  // Workaround for blocked text selection in Editor
  mxEvent.addListener(input, 'mousedown', function (evt) {
    if (evt.stopPropagation) {
      evt.stopPropagation();
    }

    evt.cancelBubble = true;
  });

  // Workaround for blocked text selection in Editor
  mxEvent.addListener(input, 'selectstart', function (evt) {
    if (evt.stopPropagation) {
      evt.stopPropagation();
    }

    evt.cancelBubble = true;
  });

  var outer = document.createElement('div');
  outer.appendChild(div);
  this.container.appendChild(outer);

  // Keeps references to the DOM nodes
  this.palettes['search'] = [elt, outer];
};

/**
 * Adds the general palette to the sidebar.
 */
Sidebar.prototype.insertSearchHint = function (div, searchTerm, count, page, results, len, more, terms) {
  if (results.length == 0 && page == 1) {
    var err = document.createElement('div');
    err.className = 'geTitle';
    err.style.cssText =
      'background-color:transparent;border-color:transparent;' +
      'color:gray;padding:6px 0px 0px 0px !important;margin:4px 8px 4px 8px;' +
      'text-align:center;cursor:default !important';

    mxUtils.write(err, mxResources.get('noResultsFor', [searchTerm]));
    div.appendChild(err);
  }
};

/**
 * Adds the general palette to the sidebar.
 */
Sidebar.prototype.addGeneralPalette = function (expand) {
  var lineTags = 'line lines connector connectors connection connections arrow arrows ';
  this.setCurrentSearchEntryLibrary('general', 'general');
  var sb = this;

  var temp = parseInt(this.editorUi.editor.graph.defaultVertexStyle['fontSize']);
  var fontSize = !isNaN(temp) ? 'fontSize=' + Math.min(16, temp) + ';' : '';

  // Reusable cells
  var field = new mxCell(
    'List Item',
    new mxGeometry(0, 0, 80, 30),
    'text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;' +
      'spacingLeft=4;spacingRight=4;overflow=hidden;points=[[0,0.5],[1,0.5]];' +
      'portConstraint=eastwest;rotatable=0;' +
      fontSize
  );
  field.vertex = true;

  var fns = [
    this.createVertexTemplateEntry(
      'rounded=0;whiteSpace=wrap;html=1;',
      120,
      60,
      '',
      'Rectangle',
      null,
      null,
      'rect rectangle box'
    ),
    this.createVertexTemplateEntry(
      'rounded=1;whiteSpace=wrap;html=1;',
      120,
      60,
      '',
      'Rounded Rectangle',
      null,
      null,
      'rounded rect rectangle box'
    ),
    // Explicit strokecolor/fillcolor=none is a workaround to maintain transparent background regardless of current style
    this.createVertexTemplateEntry(
      'text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;',
      60,
      30,
      'Text',
      'Text',
      null,
      null,
      'text textbox textarea label'
    ),
    this.createVertexTemplateEntry(
      'text;html=1;strokeColor=none;fillColor=none;spacing=5;spacingTop=-20;whiteSpace=wrap;overflow=hidden;rounded=0;',
      190,
      120,
      '<h1>Heading</h1><p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>',
      'Textbox',
      null,
      null,
      'text textbox textarea'
    ),
    this.createVertexTemplateEntry(
      'ellipse;shape=ellipse;whiteSpace=wrap;html=1;aspect=fixed;',
      120,
      80,
      '',
      'Ellipse',
      null,
      null,
      'oval ellipse state'
    ),
    this.createVertexTemplateEntry('whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Square', null, null, 'square'),
    this.createVertexTemplateEntry(
      'ellipse;shape=ellipse;whiteSpace=wrap;html=1;aspect=fixed;',
      80,
      80,
      '',
      'Circle',
      null,
      null,
      'circle'
    ),
    this.createVertexTemplateEntry(
      'shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;',
      120,
      60,
      '',
      'Process',
      null,
      null,
      'process task'
    ),
    this.createVertexTemplateEntry(
      'html=1;whiteSpace=wrap;aspect=fixed;shape=isoRectangle;',
      80,
      80,
      '',
      'Diamond',
      null,
      null,
      'diamond rhombus if condition decision conditional question test'
    ),
    this.createVertexTemplateEntry(
      'shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;',
      120,
      60,
      '',
      'Parallelogram'
    ),
    this.createVertexTemplateEntry(
      'shape=hexagon;perimeter=hexagonPerimeter2;whiteSpace=wrap;html=1;fixedSize=1;',
      120,
      80,
      '',
      'Hexagon',
      null,
      null,
      'hexagon preparation'
    ),
    this.createVertexTemplateEntry(
      'triangle;shape=triangle;whiteSpace=wrap;html=1;',
      60,
      80,
      '',
      'Triangle',
      null,
      null,
      'triangle logic inverter buffer'
    ),
    this.createVertexTemplateEntry(
      'shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;',
      60,
      80,
      '',
      'Cylinder',
      null,
      null,
      'cylinder data database'
    ),
    this.createVertexTemplateEntry(
      'ellipse;shape=cloud;whiteSpace=wrap;html=1;',
      120,
      80,
      '',
      'Cloud',
      null,
      null,
      'cloud network'
    ),
    this.createVertexTemplateEntry('shape=document;whiteSpace=wrap;html=1;boundedLbl=1;', 120, 80, '', 'Document'),
    this.createVertexTemplateEntry(
      'shape=internalStorage;whiteSpace=wrap;html=1;backgroundOutline=1;',
      80,
      80,
      '',
      'Internal Storage'
    ),
    this.createVertexTemplateEntry(
      'shape=cube;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;darkOpacity=0.05;darkOpacity2=0.1;',
      120,
      80,
      '',
      'Cube'
    ),
    this.createVertexTemplateEntry(
      'shape=step;perimeter=stepPerimeter;whiteSpace=wrap;html=1;fixedSize=1;',
      120,
      80,
      '',
      'Step'
    ),
    this.createVertexTemplateEntry(
      'shape=trapezoid;perimeter=trapezoidPerimeter;whiteSpace=wrap;html=1;fixedSize=1;',
      120,
      60,
      '',
      'Trapezoid'
    ),
    this.createVertexTemplateEntry('shape=tape;whiteSpace=wrap;html=1;', 120, 100, '', 'Tape'),
    this.createVertexTemplateEntry(
      'shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;',
      80,
      100,
      '',
      'Note'
    ),
    this.createVertexTemplateEntry('shape=card;whiteSpace=wrap;html=1;', 80, 100, '', 'Card'),
    this.createVertexTemplateEntry(
      'shape=callout;whiteSpace=wrap;html=1;perimeter=calloutPerimeter;',
      120,
      80,
      '',
      'Callout',
      null,
      null,
      'bubble chat thought speech message'
    ),
    this.createVertexTemplateEntry(
      'shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;',
      30,
      60,
      'Actor',
      'Actor',
      false,
      null,
      'user person human stickman'
    ),
    this.createVertexTemplateEntry('shape=xor;whiteSpace=wrap;html=1;', 60, 80, '', 'Or', null, null, 'logic or'),
    this.createVertexTemplateEntry('shape=or;whiteSpace=wrap;html=1;', 60, 80, '', 'And', null, null, 'logic and'),
    this.createVertexTemplateEntry(
      'shape=dataStorage;whiteSpace=wrap;html=1;fixedSize=1;',
      100,
      80,
      '',
      'Data Storage'
    ),
    this.createVertexTemplateEntry(
      'swimlane;startSize=0;',
      200,
      200,
      '',
      'Container',
      null,
      null,
      'container swimlane lane pool group'
    ),
    this.createVertexTemplateEntry(
      'swimlane;',
      200,
      200,
      'Vertical Container',
      'Container',
      null,
      null,
      'container swimlane lane pool group'
    ),
    this.createVertexTemplateEntry(
      'swimlane;horizontal=0;',
      200,
      200,
      'Horizontal Container',
      'Horizontal Container',
      null,
      null,
      'container swimlane lane pool group'
    ),
    this.addEntry('list group erd table', function () {
      var cell = new mxCell(
        'List',
        new mxGeometry(0, 0, 140, 120),
        'swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;' +
          'resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;'
      );
      cell.vertex = true;
      cell.insert(sb.cloneCell(field, 'Item 1'));
      cell.insert(sb.cloneCell(field, 'Item 2'));
      cell.insert(sb.cloneCell(field, 'Item 3'));

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'List');
    }),
    this.addEntry('list item entry value group erd table', function () {
      return sb.createVertexTemplateFromCells(
        [sb.cloneCell(field, 'List Item')],
        field.geometry.width,
        field.geometry.height,
        'List Item'
      );
    }),
    this.addEntry(
      'curve',
      mxUtils.bind(this, function () {
        var cell = new mxCell('', new mxGeometry(0, 0, 50, 50), 'curved=1;endArrow=classic;html=1;');
        cell.geometry.setTerminalPoint(new mxPoint(0, 50), true);
        cell.geometry.setTerminalPoint(new mxPoint(50, 0), false);
        cell.geometry.points = [new mxPoint(50, 50), new mxPoint(0, 0)];
        cell.geometry.relative = true;
        cell.edge = true;

        return this.createEdgeTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Curve');
      })
    ),
    this.createEdgeTemplateEntry(
      'shape=flexArrow;endArrow=classic;startArrow=classic;html=1;',
      100,
      100,
      '',
      'Bidirectional Arrow',
      null,
      lineTags + 'bidirectional'
    ),
    this.createEdgeTemplateEntry(
      'shape=flexArrow;endArrow=classic;html=1;',
      50,
      50,
      '',
      'Arrow',
      null,
      lineTags + 'directional directed'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=none;dashed=1;html=1;',
      50,
      50,
      '',
      'Dashed Line',
      null,
      lineTags + 'dashed undirected no'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;',
      50,
      50,
      '',
      'Dotted Line',
      null,
      lineTags + 'dotted undirected no'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=none;html=1;',
      50,
      50,
      '',
      'Line',
      null,
      lineTags + 'simple undirected plain blank no'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=classic;startArrow=classic;html=1;',
      50,
      50,
      '',
      'Bidirectional Connector',
      null,
      lineTags + 'bidirectional'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=classic;html=1;',
      50,
      50,
      '',
      'Directional Connector',
      null,
      lineTags + 'directional directed'
    ),
    this.createEdgeTemplateEntry('shape=link;html=1;', 100, 0, '', 'Link', null, lineTags + 'link'),
    this.addEntry(
      lineTags + 'edge title',
      mxUtils.bind(this, function () {
        var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'endArrow=classic;html=1;');
        edge.geometry.setTerminalPoint(new mxPoint(0, 0), true);
        edge.geometry.setTerminalPoint(new mxPoint(100, 0), false);
        edge.geometry.relative = true;
        edge.edge = true;

        var cell0 = new mxCell(
          'Label',
          new mxGeometry(0, 0, 0, 0),
          'edgeLabel;resizable=0;html=1;align=center;verticalAlign=middle;'
        );
        cell0.geometry.relative = true;
        cell0.setConnectable(false);
        cell0.vertex = true;
        edge.insert(cell0);

        return this.createEdgeTemplateFromCells([edge], 100, 0, 'Connector with Label');
      })
    ),
    this.addEntry(
      lineTags + 'edge title multiplicity',
      mxUtils.bind(this, function () {
        var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'endArrow=classic;html=1;');
        edge.geometry.setTerminalPoint(new mxPoint(0, 0), true);
        edge.geometry.setTerminalPoint(new mxPoint(160, 0), false);
        edge.geometry.relative = true;
        edge.edge = true;

        var cell0 = new mxCell(
          'Label',
          new mxGeometry(0, 0, 0, 0),
          'edgeLabel;resizable=0;html=1;align=center;verticalAlign=middle;'
        );
        cell0.geometry.relative = true;
        cell0.setConnectable(false);
        cell0.vertex = true;
        edge.insert(cell0);

        var cell1 = new mxCell(
          'Source',
          new mxGeometry(-1, 0, 0, 0),
          'edgeLabel;resizable=0;html=1;align=left;verticalAlign=bottom;'
        );
        cell1.geometry.relative = true;
        cell1.setConnectable(false);
        cell1.vertex = true;
        edge.insert(cell1);

        return this.createEdgeTemplateFromCells([edge], 160, 0, 'Connector with 2 Labels');
      })
    ),
    this.addEntry(
      lineTags + 'edge title multiplicity',
      mxUtils.bind(this, function () {
        var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'endArrow=classic;html=1;');
        edge.geometry.setTerminalPoint(new mxPoint(0, 0), true);
        edge.geometry.setTerminalPoint(new mxPoint(160, 0), false);
        edge.geometry.relative = true;
        edge.edge = true;

        var cell0 = new mxCell(
          'Label',
          new mxGeometry(0, 0, 0, 0),
          'edgeLabel;resizable=0;html=1;align=center;verticalAlign=middle;'
        );
        cell0.geometry.relative = true;
        cell0.setConnectable(false);
        cell0.vertex = true;
        edge.insert(cell0);

        var cell1 = new mxCell(
          'Source',
          new mxGeometry(-1, 0, 0, 0),
          'edgeLabel;resizable=0;html=1;align=left;verticalAlign=bottom;'
        );
        cell1.geometry.relative = true;
        cell1.setConnectable(false);
        cell1.vertex = true;
        edge.insert(cell1);

        var cell2 = new mxCell(
          'Target',
          new mxGeometry(1, 0, 0, 0),
          'edgeLabel;resizable=0;html=1;align=right;verticalAlign=bottom;'
        );
        cell2.geometry.relative = true;
        cell2.setConnectable(false);
        cell2.vertex = true;
        edge.insert(cell2);

        return this.createEdgeTemplateFromCells([edge], 160, 0, 'Connector with 3 Labels');
      })
    ),
    this.addEntry(
      lineTags + 'edge shape symbol message mail email',
      mxUtils.bind(this, function () {
        var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'endArrow=classic;html=1;');
        edge.geometry.setTerminalPoint(new mxPoint(0, 0), true);
        edge.geometry.setTerminalPoint(new mxPoint(100, 0), false);
        edge.geometry.relative = true;
        edge.edge = true;

        var cell = new mxCell('', new mxGeometry(0, 0, 20, 14), 'shape=message;html=1;outlineConnect=0;');
        cell.geometry.relative = true;
        cell.vertex = true;
        cell.geometry.offset = new mxPoint(-10, -7);
        edge.insert(cell);

        return this.createEdgeTemplateFromCells([edge], 100, 0, 'Connector with Symbol');
      })
    ),
  ];

  this.addPaletteFunctions('general', mxResources.get('general'), expand != null ? expand : true, fns);
  this.setCurrentSearchEntryLibrary();
};

/**
 * Adds the general palette to the sidebar.
 */
Sidebar.prototype.addMiscPalette = function (expand) {
  var sb = this;
  var lineTags = 'line lines connector connectors connection connections arrow arrows ';
  this.setCurrentSearchEntryLibrary('general', 'misc');

  var fns = [
    this.createVertexTemplateEntry(
      'text;strokeColor=none;fillColor=none;html=1;fontSize=24;fontStyle=1;verticalAlign=middle;align=center;',
      100,
      40,
      'Title',
      'Title',
      null,
      null,
      'text heading title'
    ),
    this.createVertexTemplateEntry(
      'text;strokeColor=none;fillColor=none;html=1;whiteSpace=wrap;verticalAlign=middle;overflow=hidden;',
      100,
      80,
      '<ul><li>Value 1</li><li>Value 2</li><li>Value 3</li></ul>',
      'Unordered List'
    ),
    this.createVertexTemplateEntry(
      'text;strokeColor=none;fillColor=none;html=1;whiteSpace=wrap;verticalAlign=middle;overflow=hidden;',
      100,
      80,
      '<ol><li>Value 1</li><li>Value 2</li><li>Value 3</li></ol>',
      'Ordered List'
    ),
    this.addDataEntry(
      'table',
      180,
      120,
      'Table 1',
      '7ZjBTuMwEIafJteVnVDoXpuycGAvsC9g6mltyfFE9kAann7txN2qqIgU0aCllRJpZjxO7G9i/3KyoqzWN07U6jdKMFlxnRWlQ6TeqtYlGJPlTMusmGd5zsKd5b/eaOVdK6uFA0tDOuR9h2dhnqCP9AFPrUkBr0QdTRKPMTRTVIVhznkwG6UJHmqxiO1NmESIeRKOHvRLDLHgL9CS0BZc6rNAY0TtdfewPkNpI+9Ei0+0ec3Gm6XhgSNYvznFLpTmdwNYAbk2pDRakkoZ0x4DU6BXatMtsWHC94HVv75bYsFI0PYDLA4EeI9NZIhOv0QwJjF4Tc03ujLCwi0I+So0Q9mmEGGdLANLSuYjEmGVHJemy/aSlw7rP8KtYJOy1MaUaDAWy6KN5a5RW+oATWbhCshK9mOSTcLMyuDzrR+umO6oROvJhaLHx4Lw1IAfXMz8Y8W8+IRaXgyvZRgxaWHuYUHCroasi7AObMze0t8D+7CCYkC5NPGDmistJdihjIt3GV8eCfHkxBGvd/GOQPzyTHxnsx8B+dVZE0bRhHa3ZGNIxPRUVtPVl0nEzxNHPL5EcHZGPrZGcH4WiTFFYjqiSPADTtX/93ri7x+9j7aADjh5f0/IXyAU3+GE3O1L4K6fod+e+CfV4YjqEdztL8GubeeP4V8='
    ),
    this.addDataEntry(
      'table',
      180,
      120,
      'Table 2',
      '7ZhRb5swEMc/Da+TDSFJX0O27qF7aae9u8EJlowP2ZcR+ulng1maJlbTaaEPIBHpfL5z8O/v0wlHSVYe7jWrih+QcxklX6Mk0wDYWeUh41JGMRF5lKyjOCb2F8XfArO0nSUV01zhNQlxl/CbyT3vPJ3DYCO9wxSsciayZ+daFVja11xTa9aFQP5UsY2br+0mrM8g0/gkXpyL2PEGFDKhuPY5G5CSVUa0i3URhZD5A2tgj/3f9CMXvS/Vg803PlpD/Xro359r5Icgg9blAdxzKDnqxobUIsfCRyw7TqTgYlf0aR4eYaZz7P7mHpFaw1O9TDj5IOFHqB1k0OLFkZN+n2+xmlqUkin+nbP8jWsFeeNdCJW3JN+iN58BEcoep98uuShNrqH6yfSO9yFbIWUGEpyaCpQ7DxUIhS2gdGUfiywjX9IotTvL7Jgex/Zx4RozUAa1PRVuWc4M1tzgtWLG/ybm7D9oOTvT8ldrxoQGRbWvjoLJR75BpnbXVJCtGOWijzJcoP4xZcEy3Up3staFyHOu3KL2ePkDReNr4Sfvwp/fiH0aZB8uqFGwP5xyH0CKeVCKZJLidd8YQIvF1F4GaS/NqWRDdJtlsMxmIymzxad1m7sg+3Tc7IfvNpQEtZhPWgzcbiid+s2Q/WY5YL+h55cBfaEtRlJo9P2bgptV1vlFQU9/OXL6n9Bzwl/6d5MYN246dni8AG3nTu5H/wA='
    ),
    this.addDataEntry(
      'table title',
      180,
      150,
      'Table with Title 1',
      '7ZjBbtswDEC/xtfBsuumu8bZusN2afoDasxYAmjJkNk57tePkpVlXdMlBRYXaAI4AEmRcvgogpCTvGw2t0626oetAJP8S5KXzloapWZTAmKSpbpK8kWSZSn/kuzrK6sirKatdGDomIBsDPgp8RFGy718QBitHQ0YrZ2SrRcprObzjqSjpX7ytjxlw8oaktqAY4MIOqJsOx3cF8FDaay+y8E+0najrTZfc/Qyvs1HS9S1YXnFafgt5/FvgiPYvJpqMMU8b8E2QG5gl15XpKLHzYgjVaBrtQ0rolF2o6H+Hbsjx0KEtx9k/gLkvxne2Z7TUtbpJ08OI6Q/uQa91w1KA99AVn+Z5rYaoolsGyWENUXxwRLZJiouppvuLU3lbHsvXQ1bl7VGLC1aX01jja94a7WhAKiY88PIyvRTkRScWcm62On8eHdHpTUdOT4VfluQHfXQ0bHFzPYXc4i4Y8kO1fbqP5T26vjScgKkJd7BiqSpQ6coajCe6l5pgmUrV961554f+8Z4710x9rB/W30tk12jP18LpasKzLHI84P9c30ixMWZI948xzsB8esL8RCQTYd8dhkRU46I2YQj4uZcumn2biPi85kjnn5EiPSCfOoZIcRlSEw5JISYcEqIl7ftD9pQ4vBV/GQd9Iab+MeE/A6T4myuyAeYn3BUsLr7LBjWnn01/AU='
    ),
    this.addDataEntry(
      'table title',
      180,
      150,
      'Table with Title 2',
      '7VhLc5swEP41XDs8ghtfwWl6cC9xpnfFLKAZITHSJpj8+q6EqJPaJG5aPw6ZwTP7lNnv034HgiRvNreatfUPVYAIkpsgybVSOFjNJgchgjjkRZAsgjgO6RfE3yaykcuGLdMg8ZCGeGh4YuIRhsg9exAwRA32wkdNzVprossmmUGmccWfbSwJKbBWEhmXoCkQOV8I1hruyheuouaiWLJePeJ40OhlJd9Acac647u16pZ0mPGtJR2+8i9j0/6lQSNsJgd3IT/1LagGUPdU0vECa19xPYAT1sCremxLfZCZIVD97t3iSIaHcj+syQ6sbyNKo9NYtdL82eIo/NwvUXZ+xxvBJHwHVvwRylTR+xCq1lsCSvTmg0JUjXe0HzfcS1ShVXvPdAVjScmFyJVQlluppOW/VVyiAyjN6CHI8vBLGqQ0WU5+tPXpseUacyUNaroj9lhgBjsweCiZ8X4yew+3p+w9bpP/QO3VDrXRJLc0AXIm7mCNTFZucWpshL/FXc0RVi1b29KOJGBYI2mrt2zsAf/vCFYEbSnsBVvUvChAjvSBvnmCgcWDlyp5d6mujoR7uoP7T2fG4YXjzwSvCPPFUJsZ+kMuq+XQOZsg6F/I2Lwm4uVSHIuc2afenVLvZifUu6871E5zexH79uF1mp1N264nte3CsT6etk2QcQZtm39q2ym1bX5CbYvCHW6Ty164D+/T/GziFkWT6nbhYB9P3SbYOL66kbv9uOByr749/AI='
    ),
    this.addDataEntry(
      'crossfunctional cross-functional cross functional flowchart swimlane table',
      400,
      400,
      'Cross-Functional Flowchart',
      '7ZjRatswFIafxpcDy87a7nJJlvZig9HuBVT7JBLIOkZSGrtPPymS2622Gwc6z6WGXBz9HBnxfxz+cKJ0VVTXipbsB+YgovRblMTul64Uonk+N2pRrUAIW/M8StdR4vQkSjYDOsmxMy6pAmnOvZz4yw9U7MEr9oFaf9rsZWY4SuqaNwIPGaPK+GZtahGaNaOlKw29d9IyY1zk32mNe9OozWmpjf3CHX90/YvYNaMQtNT8eHXtFAXZXmn+ALegfaNToSqpzMNhi9LchQcQew7vB2Wg6vXjKL0w49mSa8ACjKrt+cBzw/ydRexdjBnwHTMvRKq9sHu62224FYPnp1mkLRZfM4PKSuR142/xYJ1gqPijdcci81a1DNcHXggq4QZoY2cjLTGvg2SwDJWArQnlPRqDRYMpGOLqXGH5i6odNMKWC7FCYd+driVKGBV0MhR03fD03zjFnST/kPuixf0noxq6uQdeg2n+aaMfOikhC/PaB+w/jmo6lGDVS/AJ1mgEP/cSTGaCpwiSiykgvOhFmM4ITyFMrqaA8LI3PzumcM7Pt8jPrtkdP0CvWuAnMLLjTuzle4zNLzO3c7lNIyxJPJM7l9w0MpKQ3pDs+J8zh+RbhGQX+fFDkrRXPR9uZsngRdCUYpK0F0MzuXcSlO3dzoxuUkl5FJudfOj4a3H/Gw=='
    ),
    this.createVertexTemplateEntry(
      'text;html=1;strokeColor=#c0c0c0;fillColor=#ffffff;overflow=fill;rounded=0;',
      280,
      160,
      '<table border="1" width="100%" height="100%" cellpadding="4" style="width:100%;height:100%;border-collapse:collapse;">' +
        '<tr style="background-color:#A7C942;color:#ffffff;border:1px solid #98bf21;"><th align="left">Title 1</th><th align="left">Title 2</th><th align="left">Title 3</th></tr>' +
        '<tr style="border:1px solid #98bf21;"><td>Value 1</td><td>Value 2</td><td>Value 3</td></tr>' +
        '<tr style="background-color:#EAF2D3;border:1px solid #98bf21;"><td>Value 4</td><td>Value 5</td><td>Value 6</td></tr>' +
        '<tr style="border:1px solid #98bf21;"><td>Value 7</td><td>Value 8</td><td>Value 9</td></tr>' +
        '<tr style="background-color:#EAF2D3;border:1px solid #98bf21;"><td>Value 10</td><td>Value 11</td><td>Value 12</td></tr></table>',
      'HTML Table 1'
    ),
    this.createVertexTemplateEntry(
      'text;html=1;strokeColor=#c0c0c0;fillColor=none;overflow=fill;',
      180,
      140,
      '<table border="0" width="100%" height="100%" style="width:100%;height:100%;border-collapse:collapse;">' +
        '<tr><td align="center">Value 1</td><td align="center">Value 2</td><td align="center">Value 3</td></tr>' +
        '<tr><td align="center">Value 4</td><td align="center">Value 5</td><td align="center">Value 6</td></tr>' +
        '<tr><td align="center">Value 7</td><td align="center">Value 8</td><td align="center">Value 9</td></tr></table>',
      'HTML Table 2'
    ),
    this.createVertexTemplateEntry(
      'text;html=1;strokeColor=none;fillColor=none;overflow=fill;',
      180,
      140,
      '<table border="1" width="100%" height="100%" style="width:100%;height:100%;border-collapse:collapse;">' +
        '<tr><td align="center">Value 1</td><td align="center">Value 2</td><td align="center">Value 3</td></tr>' +
        '<tr><td align="center">Value 4</td><td align="center">Value 5</td><td align="center">Value 6</td></tr>' +
        '<tr><td align="center">Value 7</td><td align="center">Value 8</td><td align="center">Value 9</td></tr></table>',
      'HTML Table 3'
    ),
    this.createVertexTemplateEntry(
      'text;html=1;strokeColor=none;fillColor=none;overflow=fill;',
      160,
      140,
      '<table border="1" width="100%" height="100%" cellpadding="4" style="width:100%;height:100%;border-collapse:collapse;">' +
        '<tr><th align="center"><b>Title</b></th></tr>' +
        '<tr><td align="center">Section 1.1\nSection 1.2\nSection 1.3</td></tr>' +
        '<tr><td align="center">Section 2.1\nSection 2.2\nSection 2.3</td></tr></table>',
      'HTML Table 4'
    ),
    this.addEntry(
      'link hyperlink',
      mxUtils.bind(this, function () {
        var cell = new mxCell(
          'Link',
          new mxGeometry(0, 0, 60, 40),
          'text;html=1;strokeColor=none;fillColor=none;whiteSpace=wrap;align=center;verticalAlign=middle;fontColor=#0000EE;fontStyle=4;'
        );
        cell.vertex = true;
        this.graph.setLinkForCell(cell, 'https://www.draw.io');

        return this.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Link');
      })
    ),
    this.addEntry(
      'timestamp date time text label',
      mxUtils.bind(this, function () {
        var cell = new mxCell(
          '%date{ddd mmm dd yyyy HH:MM:ss}%',
          new mxGeometry(0, 0, 160, 20),
          'text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;overflow=hidden;'
        );
        cell.vertex = true;
        this.graph.setAttributeForCell(cell, 'placeholders', '1');

        return this.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Timestamp');
      })
    ),
    this.addEntry(
      'variable placeholder metadata hello world text label',
      mxUtils.bind(this, function () {
        var cell = new mxCell(
          '%name% Text',
          new mxGeometry(0, 0, 80, 20),
          'text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;overflow=hidden;'
        );
        cell.vertex = true;
        this.graph.setAttributeForCell(cell, 'placeholders', '1');
        this.graph.setAttributeForCell(cell, 'name', 'Variable');

        return this.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Variable');
      })
    ),
    this.createVertexTemplateEntry(
      'shape=ext;double=1;rounded=0;whiteSpace=wrap;html=1;',
      120,
      80,
      '',
      'Double Rectangle',
      null,
      null,
      'rect rectangle box double'
    ),
    this.createVertexTemplateEntry(
      'shape=ext;double=1;rounded=1;whiteSpace=wrap;html=1;',
      120,
      80,
      '',
      'Double Rounded Rectangle',
      null,
      null,
      'rounded rect rectangle box double'
    ),
    this.createVertexTemplateEntry(
      'ellipse;shape=doubleEllipse;whiteSpace=wrap;html=1;',
      100,
      60,
      '',
      'Double Ellipse',
      null,
      null,
      'oval ellipse start end state double'
    ),
    this.createVertexTemplateEntry(
      'shape=ext;double=1;whiteSpace=wrap;html=1;aspect=fixed;',
      80,
      80,
      '',
      'Double Square',
      null,
      null,
      'double square'
    ),
    this.createVertexTemplateEntry(
      'ellipse;shape=doubleEllipse;whiteSpace=wrap;html=1;aspect=fixed;',
      80,
      80,
      '',
      'Double Circle',
      null,
      null,
      'double circle'
    ),
    this.createVertexTemplateEntry(
      'rounded=1;whiteSpace=wrap;html=1;strokeWidth=2;fillWeight=4;hachureGap=8;hachureAngle=45;fillColor=#1ba1e2;sketch=1;',
      120,
      60,
      '',
      'Rectangle Sketch',
      true,
      null,
      'rectangle rect box text sketch comic retro'
    ),
    this.createVertexTemplateEntry(
      'ellipse;whiteSpace=wrap;html=1;strokeWidth=2;fillWeight=2;hachureGap=8;fillColor=#990000;fillStyle=dots;sketch=1;',
      120,
      60,
      '',
      'Ellipse Sketch',
      true,
      null,
      'ellipse oval sketch comic retro'
    ),
    this.createVertexTemplateEntry(
      'rhombus;whiteSpace=wrap;html=1;strokeWidth=2;fillWeight=-1;hachureGap=8;fillStyle=cross-hatch;fillColor=#006600;sketch=1;',
      120,
      60,
      '',
      'Diamond Sketch',
      true,
      null,
      'diamond sketch comic retro'
    ),
    this.createVertexTemplateEntry(
      'html=1;whiteSpace=wrap;shape=isoCube2;backgroundOutline=1;isoAngle=15;',
      90,
      100,
      '',
      'Isometric Cube',
      true,
      null,
      'cube box iso isometric'
    ),
    this.createVertexTemplateEntry(
      'html=1;whiteSpace=wrap;aspect=fixed;shape=isoRectangle;',
      150,
      90,
      '',
      'Isometric Square',
      true,
      null,
      'rectangle rect box iso isometric'
    ),
    this.createEdgeTemplateEntry('edgeStyle=isometricEdgeStyle;endArrow=none;html=1;', 50, 100, '', 'Isometric Edge 1'),
    this.createEdgeTemplateEntry(
      'edgeStyle=isometricEdgeStyle;endArrow=none;html=1;elbow=vertical;',
      50,
      100,
      '',
      'Isometric Edge 2'
    ),
    this.createVertexTemplateEntry(
      'shape=curlyBracket;whiteSpace=wrap;html=1;rounded=1;',
      20,
      120,
      '',
      'Left Curly Bracket'
    ),
    this.createVertexTemplateEntry(
      'shape=curlyBracket;whiteSpace=wrap;html=1;rounded=1;flipH=1;',
      20,
      120,
      '',
      'Right Curly Bracket'
    ),
    this.createVertexTemplateEntry('line;strokeWidth=2;html=1;', 160, 10, '', 'Horizontal Line'),
    this.createVertexTemplateEntry('line;strokeWidth=2;direction=south;html=1;', 10, 160, '', 'Vertical Line'),
    this.createVertexTemplateEntry(
      'line;strokeWidth=4;html=1;perimeter=backbonePerimeter;points=[];outlineConnect=0;',
      160,
      10,
      '',
      'Horizontal Backbone',
      false,
      null,
      'backbone bus network'
    ),
    this.createVertexTemplateEntry(
      'line;strokeWidth=4;direction=south;html=1;perimeter=backbonePerimeter;points=[];outlineConnect=0;',
      10,
      160,
      '',
      'Vertical Backbone',
      false,
      null,
      'backbone bus network'
    ),
    this.createVertexTemplateEntry(
      'shape=crossbar;whiteSpace=wrap;html=1;rounded=1;',
      120,
      20,
      '',
      'Horizontal Crossbar',
      false,
      null,
      'crossbar distance measure dimension unit'
    ),
    this.createVertexTemplateEntry(
      'shape=crossbar;whiteSpace=wrap;html=1;rounded=1;direction=south;',
      20,
      120,
      '',
      'Vertical Crossbar',
      false,
      null,
      'crossbar distance measure dimension unit'
    ),
    this.createVertexTemplateEntry(
      'shape=image;html=1;verticalLabelPosition=bottom;verticalAlign=top;imageAspect=1;aspect=fixed;image=' +
        this.gearImage,
      52,
      61,
      '',
      'Image (Fixed Aspect)',
      false,
      null,
      'fixed image icon symbol'
    ),
    this.createVertexTemplateEntry(
      'shape=image;html=1;verticalLabelPosition=bottom;verticalAlign=top;imageAspect=0;image=' + this.gearImage,
      50,
      60,
      '',
      'Image (Variable Aspect)',
      false,
      null,
      'strechted image icon symbol'
    ),
    this.createVertexTemplateEntry(
      'icon;html=1;image=' + this.gearImage,
      60,
      60,
      'Icon',
      'Icon',
      false,
      null,
      'icon image symbol'
    ),
    this.createVertexTemplateEntry(
      'label;whiteSpace=wrap;html=1;image=' + this.gearImage,
      140,
      60,
      'Label',
      'Label 1',
      null,
      null,
      'label image icon symbol'
    ),
    this.createVertexTemplateEntry(
      'label;whiteSpace=wrap;html=1;align=center;verticalAlign=bottom;spacingLeft=0;spacingBottom=4;imageAlign=center;imageVerticalAlign=top;image=' +
        this.gearImage,
      120,
      80,
      'Label',
      'Label 2',
      null,
      null,
      'label image icon symbol'
    ),
    this.addEntry('shape group container', function () {
      var cell = new mxCell(
        'Label',
        new mxGeometry(0, 0, 160, 70),
        'html=1;whiteSpace=wrap;container=1;recursiveResize=0;collapsible=0;'
      );
      cell.vertex = true;

      var symbol = new mxCell('', new mxGeometry(20, 20, 20, 30), 'triangle;html=1;whiteSpace=wrap;');
      symbol.vertex = true;
      cell.insert(symbol);

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Shape Group');
    }),
    this.createVertexTemplateEntry(
      'shape=partialRectangle;whiteSpace=wrap;html=1;left=0;right=0;fillColor=none;',
      120,
      60,
      '',
      'Partial Rectangle'
    ),
    this.createVertexTemplateEntry(
      'shape=partialRectangle;whiteSpace=wrap;html=1;bottom=0;top=0;fillColor=none;',
      120,
      60,
      '',
      'Partial Rectangle'
    ),
    this.createVertexTemplateEntry(
      'shape=partialRectangle;whiteSpace=wrap;html=1;bottom=0;right=0;fillColor=none;',
      120,
      60,
      '',
      'Partial Rectangle'
    ),
    this.createVertexTemplateEntry(
      'shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=none;routingCenterX=-0.5;',
      120,
      60,
      '',
      'Partial Rectangle'
    ),
    this.createVertexTemplateEntry(
      'shape=waypoint;sketch=0;fillStyle=solid;size=6;pointerEvents=1;points=[];fillColor=none;resizable=0;rotatable=0;perimeter=centerPerimeter;snapToPoint=1;',
      40,
      40,
      '',
      'Waypoint'
    ),
    this.createEdgeTemplateEntry(
      'edgeStyle=segmentEdgeStyle;endArrow=classic;html=1;',
      50,
      50,
      '',
      'Manual Line',
      null,
      lineTags + 'manual'
    ),
    this.createEdgeTemplateEntry(
      'shape=filledEdge;rounded=0;fixDash=1;endArrow=none;strokeWidth=10;fillColor=#ffffff;edgeStyle=orthogonalEdgeStyle;',
      60,
      40,
      '',
      'Filled Edge'
    ),
    this.createEdgeTemplateEntry(
      'edgeStyle=elbowEdgeStyle;elbow=horizontal;endArrow=classic;html=1;',
      50,
      50,
      '',
      'Horizontal Elbow',
      null,
      lineTags + 'elbow horizontal'
    ),
    this.createEdgeTemplateEntry(
      'edgeStyle=elbowEdgeStyle;elbow=vertical;endArrow=classic;html=1;',
      50,
      50,
      '',
      'Vertical Elbow',
      null,
      lineTags + 'elbow vertical'
    ),
  ];

  this.addPaletteFunctions('misc', mxResources.get('misc'), expand != null ? expand : true, fns);
  this.setCurrentSearchEntryLibrary();
};
/**
 * Adds the container palette to the sidebar.
 */
Sidebar.prototype.addAdvancedPalette = function (expand) {
  this.setCurrentSearchEntryLibrary('general', 'advanced');
  this.addPaletteFunctions(
    'advanced',
    mxResources.get('advanced'),
    expand != null ? expand : false,
    this.createAdvancedShapes()
  );
  this.setCurrentSearchEntryLibrary();
};

/**
 * Adds the general palette to the sidebar.
 */
Sidebar.prototype.addBasicPalette = function (dir) {
  this.setCurrentSearchEntryLibrary('basic');
  this.addStencilPalette(
    'basic',
    mxResources.get('basic'),
    dir + '/basic.xml',
    ';whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#000000;strokeWidth=2',
    null,
    null,
    null,
    null,
    [
      this.createVertexTemplateEntry(
        'shape=partialRectangle;whiteSpace=wrap;html=1;top=0;bottom=0;fillColor=none;',
        120,
        60,
        '',
        'Partial Rectangle'
      ),
      this.createVertexTemplateEntry(
        'shape=partialRectangle;whiteSpace=wrap;html=1;right=0;top=0;bottom=0;fillColor=none;routingCenterX=-0.5;',
        120,
        60,
        '',
        'Partial Rectangle'
      ),
      this.createVertexTemplateEntry(
        'shape=partialRectangle;whiteSpace=wrap;html=1;bottom=0;right=0;fillColor=none;',
        120,
        60,
        '',
        'Partial Rectangle'
      ),
      this.createVertexTemplateEntry(
        'shape=partialRectangle;whiteSpace=wrap;html=1;top=0;left=0;fillColor=none;',
        120,
        60,
        '',
        'Partial Rectangle'
      ),
    ]
  );
  this.setCurrentSearchEntryLibrary();
};

/**
 * Adds the container palette to the sidebar.
 */
Sidebar.prototype.createAdvancedShapes = function () {
  // Avoids having to bind all functions to "this"
  var sb = this;

  // Reusable cells
  var field = new mxCell(
    'List Item',
    new mxGeometry(0, 0, 60, 26),
    'text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
  );
  field.vertex = true;

  return [
    this.createVertexTemplateEntry(
      'shape=tapeData;whiteSpace=wrap;html=1;perimeter=ellipsePerimeter;',
      80,
      80,
      '',
      'Tape Data'
    ),
    this.createVertexTemplateEntry('shape=manualInput;whiteSpace=wrap;html=1;', 80, 80, '', 'Manual Input'),
    this.createVertexTemplateEntry('shape=loopLimit;whiteSpace=wrap;html=1;', 100, 80, '', 'Loop Limit'),
    this.createVertexTemplateEntry('shape=offPageConnector;whiteSpace=wrap;html=1;', 80, 80, '', 'Off Page Connector'),
    this.createVertexTemplateEntry('shape=delay;whiteSpace=wrap;html=1;', 80, 40, '', 'Delay'),
    this.createVertexTemplateEntry('shape=display;whiteSpace=wrap;html=1;', 80, 40, '', 'Display'),
    this.createVertexTemplateEntry(
      'shape=singleArrow;direction=west;whiteSpace=wrap;html=1;',
      100,
      60,
      '',
      'Arrow Left'
    ),
    this.createVertexTemplateEntry('shape=singleArrow;whiteSpace=wrap;html=1;', 100, 60, '', 'Arrow Right'),
    this.createVertexTemplateEntry(
      'shape=singleArrow;direction=north;whiteSpace=wrap;html=1;',
      60,
      100,
      '',
      'Arrow Up'
    ),
    this.createVertexTemplateEntry(
      'shape=singleArrow;direction=south;whiteSpace=wrap;html=1;',
      60,
      100,
      '',
      'Arrow Down'
    ),
    this.createVertexTemplateEntry('shape=doubleArrow;whiteSpace=wrap;html=1;', 100, 60, '', 'Double Arrow'),
    this.createVertexTemplateEntry(
      'shape=doubleArrow;direction=south;whiteSpace=wrap;html=1;',
      60,
      100,
      '',
      'Double Arrow Vertical',
      null,
      null,
      'double arrow'
    ),
    this.createVertexTemplateEntry(
      'shape=actor;whiteSpace=wrap;html=1;',
      40,
      60,
      '',
      'User',
      null,
      null,
      'user person human'
    ),
    this.createVertexTemplateEntry('shape=cross;whiteSpace=wrap;html=1;', 80, 80, '', 'Cross'),
    this.createVertexTemplateEntry('shape=corner;whiteSpace=wrap;html=1;', 80, 80, '', 'Corner'),
    this.createVertexTemplateEntry('shape=tee;whiteSpace=wrap;html=1;', 80, 80, '', 'Tee'),
    this.createVertexTemplateEntry(
      'shape=datastore;whiteSpace=wrap;html=1;',
      60,
      60,
      '',
      'Data Store',
      null,
      null,
      'data store cylinder database'
    ),
    this.createVertexTemplateEntry(
      'shape=orEllipse;perimeter=ellipsePerimeter;whiteSpace=wrap;html=1;backgroundOutline=1;',
      80,
      80,
      '',
      'Or',
      null,
      null,
      'or circle oval ellipse'
    ),
    this.createVertexTemplateEntry(
      'shape=sumEllipse;perimeter=ellipsePerimeter;whiteSpace=wrap;html=1;backgroundOutline=1;',
      80,
      80,
      '',
      'Sum',
      null,
      null,
      'sum circle oval ellipse'
    ),
    this.createVertexTemplateEntry(
      'shape=lineEllipse;perimeter=ellipsePerimeter;whiteSpace=wrap;html=1;backgroundOutline=1;',
      80,
      80,
      '',
      'Ellipse with horizontal divider',
      null,
      null,
      'circle oval ellipse'
    ),
    this.createVertexTemplateEntry(
      'shape=lineEllipse;line=vertical;perimeter=ellipsePerimeter;whiteSpace=wrap;html=1;backgroundOutline=1;',
      80,
      80,
      '',
      'Ellipse with vertical divider',
      null,
      null,
      'circle oval ellipse'
    ),
    this.createVertexTemplateEntry(
      'shape=sortShape;perimeter=rhombusPerimeter;whiteSpace=wrap;html=1;',
      80,
      80,
      '',
      'Sort',
      null,
      null,
      'sort'
    ),
    this.createVertexTemplateEntry(
      'shape=collate;whiteSpace=wrap;html=1;',
      80,
      80,
      '',
      'Collate',
      null,
      null,
      'collate'
    ),
    this.createVertexTemplateEntry(
      'shape=switch;whiteSpace=wrap;html=1;',
      60,
      60,
      '',
      'Switch',
      null,
      null,
      'switch router'
    ),
    this.addEntry('process bar', function () {
      return sb.createVertexTemplateFromData(
        'zZXRaoMwFIafJpcDjbNrb2233rRQ8AkyPdPQaCRJV+3T7yTG2rUVBoOtgpDzn/xJzncCIdGyateKNeVW5iBI9EqipZLS9KOqXYIQhAY8J9GKUBrgT+jbRDZ02aBhCmrzEwPtDZ9MHKBXdkpmoDWKCVN9VptO+Kw+8kqwGqMkK7nIN6yTB7uTNizbD1FSSsVPsjYMC1qFKHxwIZZSSIVxLZ1/nJNar5+oQPMT7IYCrqUta1ENzuqGaeOFTArBGs3f3Vmtoo2Se7ja1h00kSoHK4bBIKUNy3hdoPYU0mF91i9mT8EEL2ocZ3gKa00ayWujLZY4IfHKFonVDLsRGgXuQ90zBmWgneyTk3yT1iArMKrDKUeem9L3ajHrbSXwohxsQd/ggOleKM7ese048J2/fwuim1uQGmhQCW8vQMkacP3GCQgBFMftHEsr7cYYe95CnmKTPMFbYD8CQ++DGQy+/M5X4ku5wHYmdIktfvk9tecpavThqS3m/0YtnqIWPTy1cD77K2wYjo+Ay317I74A',
        296,
        100,
        'Process Bar'
      );
    }),
    this.createVertexTemplateEntry(
      'swimlane;',
      200,
      200,
      'Container',
      'Container',
      null,
      null,
      'container swimlane lane pool group'
    ),
    this.addEntry('list group erd table', function () {
      var cell = new mxCell(
        'List',
        new mxGeometry(0, 0, 140, 110),
        'swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;' +
          'resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;'
      );
      cell.vertex = true;
      cell.insert(sb.cloneCell(field, 'Item 1'));
      cell.insert(sb.cloneCell(field, 'Item 2'));
      cell.insert(sb.cloneCell(field, 'Item 3'));

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'List');
    }),
    this.addEntry('list item entry value group erd table', function () {
      return sb.createVertexTemplateFromCells(
        [sb.cloneCell(field, 'List Item')],
        field.geometry.width,
        field.geometry.height,
        'List Item'
      );
    }),
  ];
};

/**
 * Adds the general palette to the sidebar.
 */
Sidebar.prototype.addUmlPalette = function (expand) {
  // Avoids having to bind all functions to "this"
  var sb = this;

  // Reusable cells
  var field = new mxCell(
    '+ field: type',
    new mxGeometry(0, 0, 100, 26),
    'text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
  );
  field.vertex = true;

  var divider = new mxCell(
    '',
    new mxGeometry(0, 0, 40, 8),
    'line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;'
  );
  divider.vertex = true;

  // Default tags
  var dt = 'uml static class ';
  this.setCurrentSearchEntryLibrary('uml');

  var fns = [
    this.createVertexTemplateEntry('html=1;', 110, 50, 'Object', 'Object', null, null, dt + 'object instance'),
    this.createVertexTemplateEntry(
      'html=1;',
      110,
      50,
      '&laquo;interface&raquo;<br><b>Name</b>',
      'Interface',
      null,
      null,
      dt + 'interface object instance annotated annotation'
    ),
    this.addEntry(dt + 'object instance', function () {
      var cell = new mxCell(
        'Classname',
        new mxGeometry(0, 0, 160, 90),
        'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;'
      );
      cell.vertex = true;
      cell.insert(field.clone());
      cell.insert(divider.clone());
      cell.insert(sb.cloneCell(field, '+ method(type): type'));

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Class');
    }),
    this.addEntry(dt + 'section subsection', function () {
      var cell = new mxCell(
        'Classname',
        new mxGeometry(0, 0, 140, 110),
        'swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;'
      );
      cell.vertex = true;
      cell.insert(field.clone());
      cell.insert(field.clone());
      cell.insert(field.clone());

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Class 2');
    }),
    this.addEntry(dt + 'item member method function variable field attribute label', function () {
      return sb.createVertexTemplateFromCells(
        [sb.cloneCell(field, '+ item: attribute')],
        field.geometry.width,
        field.geometry.height,
        'Item 1'
      );
    }),
    this.addEntry(dt + 'item member method function variable field attribute label', function () {
      var cell = new mxCell(
        'item: attribute',
        new mxGeometry(0, 0, 120, field.geometry.height),
        'label;fontStyle=0;strokeColor=none;fillColor=none;align=left;verticalAlign=top;overflow=hidden;' +
          'spacingLeft=28;spacingRight=4;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;imageWidth=16;imageHeight=16;image=' +
          sb.gearImage
      );
      cell.vertex = true;

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Item 2');
    }),
    this.addEntry(dt + 'divider hline line separator', function () {
      return sb.createVertexTemplateFromCells(
        [divider.clone()],
        divider.geometry.width,
        divider.geometry.height,
        'Divider'
      );
    }),
    this.addEntry(dt + 'spacer space gap separator', function () {
      var cell = new mxCell(
        '',
        new mxGeometry(0, 0, 20, 14),
        'text;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=4;spacingRight=4;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;'
      );
      cell.vertex = true;

      return sb.createVertexTemplateFromCells([cell.clone()], cell.geometry.width, cell.geometry.height, 'Spacer');
    }),
    this.createVertexTemplateEntry(
      'text;align=center;fontStyle=1;verticalAlign=middle;spacingLeft=3;spacingRight=3;strokeColor=none;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;',
      80,
      26,
      'Title',
      'Title',
      null,
      null,
      dt + 'title label'
    ),
    this.addEntry(dt + 'component', function () {
      var cell = new mxCell(
        '&laquo;Annotation&raquo;<br/><b>Component</b>',
        new mxGeometry(0, 0, 180, 90),
        'html=1;dropTarget=0;'
      );
      cell.vertex = true;

      var symbol = new mxCell('', new mxGeometry(1, 0, 20, 20), 'shape=module;jettyWidth=8;jettyHeight=4;');
      symbol.vertex = true;
      symbol.geometry.relative = true;
      symbol.geometry.offset = new mxPoint(-27, 7);
      cell.insert(symbol);

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Component');
    }),
    this.addEntry(dt + 'component', function () {
      var cell = new mxCell(
        '<p style="margin:0px;margin-top:6px;text-align:center;"><b>Component</b></p>' +
          '<hr/><p style="margin:0px;margin-left:8px;">+ Attribute1: Type<br/>+ Attribute2: Type</p>',
        new mxGeometry(0, 0, 180, 90),
        'align=left;overflow=fill;html=1;dropTarget=0;'
      );
      cell.vertex = true;

      var symbol = new mxCell('', new mxGeometry(1, 0, 20, 20), 'shape=component;jettyWidth=8;jettyHeight=4;');
      symbol.vertex = true;
      symbol.geometry.relative = true;
      symbol.geometry.offset = new mxPoint(-24, 4);
      cell.insert(symbol);

      return sb.createVertexTemplateFromCells(
        [cell],
        cell.geometry.width,
        cell.geometry.height,
        'Component with Attributes'
      );
    }),
    this.createVertexTemplateEntry(
      'verticalAlign=top;align=left;spacingTop=8;spacingLeft=2;spacingRight=12;shape=cube;size=10;direction=south;fontStyle=4;html=1;',
      180,
      120,
      'Block',
      'Block',
      null,
      null,
      dt + 'block'
    ),
    this.createVertexTemplateEntry(
      'shape=module;align=left;spacingLeft=20;align=center;verticalAlign=top;',
      100,
      50,
      'Module',
      'Module',
      null,
      null,
      dt + 'module component'
    ),
    this.createVertexTemplateEntry(
      'shape=folder;fontStyle=1;spacingTop=10;tabWidth=40;tabHeight=14;tabPosition=left;html=1;',
      70,
      50,
      'package',
      'Package',
      null,
      null,
      dt + 'package'
    ),
    this.createVertexTemplateEntry(
      'verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;',
      160,
      90,
      '<p style="margin:0px;margin-top:4px;text-align:center;text-decoration:underline;"><b>Object:Type</b></p><hr/>' +
        '<p style="margin:0px;margin-left:8px;">field1 = value1<br/>field2 = value2<br>field3 = value3</p>',
      'Object',
      null,
      null,
      dt + 'object instance'
    ),
    this.createVertexTemplateEntry(
      'verticalAlign=top;align=left;overflow=fill;html=1;',
      180,
      90,
      '<div style="box-sizing:border-box;width:100%;background:#e4e4e4;padding:2px;">Tablename</div>' +
        '<table style="width:100%;font-size:1em;" cellpadding="2" cellspacing="0">' +
        '<tr><td>PK</td><td>uniqueId</td></tr><tr><td>FK1</td><td>' +
        'foreignKey</td></tr><tr><td></td><td>fieldname</td></tr></table>',
      'Entity',
      null,
      null,
      'er entity table'
    ),
    this.addEntry(dt + 'object instance', function () {
      var cell = new mxCell(
        '<p style="margin:0px;margin-top:4px;text-align:center;">' +
          '<b>Class</b></p>' +
          '<hr size="1"/><div style="height:2px;"></div>',
        new mxGeometry(0, 0, 140, 60),
        'verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;'
      );
      cell.vertex = true;

      return sb.createVertexTemplateFromCells([cell.clone()], cell.geometry.width, cell.geometry.height, 'Class 3');
    }),
    this.addEntry(dt + 'object instance', function () {
      var cell = new mxCell(
        '<p style="margin:0px;margin-top:4px;text-align:center;">' +
          '<b>Class</b></p>' +
          '<hr size="1"/><div style="height:2px;"></div><hr size="1"/><div style="height:2px;"></div>',
        new mxGeometry(0, 0, 140, 60),
        'verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;'
      );
      cell.vertex = true;

      return sb.createVertexTemplateFromCells([cell.clone()], cell.geometry.width, cell.geometry.height, 'Class 4');
    }),
    this.addEntry(dt + 'object instance', function () {
      var cell = new mxCell(
        '<p style="margin:0px;margin-top:4px;text-align:center;">' +
          '<b>Class</b></p>' +
          '<hr size="1"/><p style="margin:0px;margin-left:4px;">+ field: Type</p><hr size="1"/>' +
          '<p style="margin:0px;margin-left:4px;">+ method(): Type</p>',
        new mxGeometry(0, 0, 160, 90),
        'verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;'
      );
      cell.vertex = true;

      return sb.createVertexTemplateFromCells([cell.clone()], cell.geometry.width, cell.geometry.height, 'Class 5');
    }),
    this.addEntry(dt + 'object instance', function () {
      var cell = new mxCell(
        '<p style="margin:0px;margin-top:4px;text-align:center;">' +
          '<i>&lt;&lt;Interface&gt;&gt;</i><br/><b>Interface</b></p>' +
          '<hr size="1"/><p style="margin:0px;margin-left:4px;">+ field1: Type<br/>' +
          '+ field2: Type</p>' +
          '<hr size="1"/><p style="margin:0px;margin-left:4px;">' +
          '+ method1(Type): Type<br/>' +
          '+ method2(Type, Type): Type</p>',
        new mxGeometry(0, 0, 190, 140),
        'verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;'
      );
      cell.vertex = true;

      return sb.createVertexTemplateFromCells([cell.clone()], cell.geometry.width, cell.geometry.height, 'Interface 2');
    }),
    this.createVertexTemplateEntry(
      'shape=providedRequiredInterface;html=1;verticalLabelPosition=bottom;',
      20,
      20,
      '',
      'Provided/Required Interface',
      null,
      null,
      'uml provided required interface lollipop notation'
    ),
    this.createVertexTemplateEntry(
      'shape=requiredInterface;html=1;verticalLabelPosition=bottom;',
      10,
      20,
      '',
      'Required Interface',
      null,
      null,
      'uml required interface lollipop notation'
    ),
    this.addEntry('uml lollipop notation provided required interface', function () {
      return sb.createVertexTemplateFromData(
        'zVTBrptADPyavVYEkt4b0uQd3pMq5dD2uAUD27dgZJwE8vX1spsQlETtpVWRIjFjex3PmFVJWvc70m31hjlYlXxWSUqI7N/qPgVrVRyZXCUbFceR/FS8fRJdjNGo1QQN/0lB7AuO2h7AM57oeLCBIDw0Obj8SCVrJK6wxEbbV8RWyIWQP4F52Juzq9AHRqEqrm2IQpN/IsKTwAYb8MzWWBuO9B0hL2E2BGsqIQyxvJ9rzApD7QBrYBokhcBqNsf5UbrzsLzmXUu/oJET42jwGat5QYcHyiDkTDLKy03TiRrFfSx08m+FrrQtUkOZvZdbFKThmwMfVhf4fQ43/W3uZriiPPT+KKhjwnf4anKuQv//wsg+NPJ7/9d9Xf7eVykwbeeMOFWGYd/qzEVO8tHP/Suw4a2ujXV/+gXsEdhkOgSC8os44BQt0tggicZHeG1N2QiXibhAV48epRayEDd8MT7Ct06TUaXVWq027tCuhcx5VZjebeeaoDNn/WMcb/p+j0AM/dNr6InLl4Lgzylsk6OCgRWYsuI592gNZh5OhgmcblPv7+1l+ws=',
        40,
        10,
        'Lollipop Notation'
      );
    }),
    this.createVertexTemplateEntry(
      'shape=umlBoundary;whiteSpace=wrap;html=1;',
      100,
      80,
      'Boundary Object',
      'Boundary Object',
      null,
      null,
      'uml boundary object'
    ),
    this.createVertexTemplateEntry(
      'ellipse;shape=umlEntity;whiteSpace=wrap;html=1;',
      80,
      80,
      'Entity Object',
      'Entity Object',
      null,
      null,
      'uml entity object'
    ),
    this.createVertexTemplateEntry(
      'ellipse;shape=umlControl;whiteSpace=wrap;html=1;',
      70,
      80,
      'Control Object',
      'Control Object',
      null,
      null,
      'uml control object'
    ),
    this.createVertexTemplateEntry(
      'shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;',
      30,
      60,
      'Actor',
      'Actor',
      false,
      null,
      'uml actor'
    ),
    this.createVertexTemplateEntry(
      'ellipse;whiteSpace=wrap;html=1;',
      140,
      70,
      'Use Case',
      'Use Case',
      null,
      null,
      'uml use case usecase'
    ),
    this.addEntry('uml activity state start', function () {
      var cell = new mxCell(
        '',
        new mxGeometry(0, 0, 30, 30),
        'ellipse;html=1;shape=startState;fillColor=#000000;strokeColor=#ff0000;'
      );
      cell.vertex = true;

      var edge = new mxCell(
        '',
        new mxGeometry(0, 0, 0, 0),
        'edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(15, 90), false);
      edge.geometry.relative = true;
      edge.edge = true;

      cell.insertEdge(edge, true);

      return sb.createVertexTemplateFromCells([cell, edge], 30, 90, 'Start');
    }),
    this.addEntry('uml activity state', function () {
      var cell = new mxCell(
        'Activity',
        new mxGeometry(0, 0, 120, 40),
        'rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;'
      );
      cell.vertex = true;

      var edge = new mxCell(
        '',
        new mxGeometry(0, 0, 0, 0),
        'edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(60, 100), false);
      edge.geometry.relative = true;
      edge.edge = true;

      cell.insertEdge(edge, true);

      return sb.createVertexTemplateFromCells([cell, edge], 120, 100, 'Activity');
    }),
    this.addEntry('uml activity composite state', function () {
      var cell = new mxCell(
        'Composite State',
        new mxGeometry(0, 0, 160, 60),
        'swimlane;html=1;fontStyle=1;align=center;verticalAlign=middle;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=0;resizeLast=1;container=0;fontColor=#000000;collapsible=0;rounded=1;arcSize=30;strokeColor=#ff0000;fillColor=#ffffc0;swimlaneFillColor=#ffffc0;dropTarget=0;'
      );
      cell.vertex = true;

      var cell1 = new mxCell(
        'Subtitle',
        new mxGeometry(0, 0, 200, 26),
        'text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;spacingLeft=4;spacingRight=4;whiteSpace=wrap;overflow=hidden;rotatable=0;fontColor=#000000;'
      );
      cell1.vertex = true;
      cell.insert(cell1);

      var edge = new mxCell(
        '',
        new mxGeometry(0, 0, 0, 0),
        'edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(80, 120), false);
      edge.geometry.relative = true;
      edge.edge = true;

      cell.insertEdge(edge, true);

      return sb.createVertexTemplateFromCells([cell, edge], 160, 120, 'Composite State');
    }),
    this.addEntry('uml activity condition', function () {
      var cell = new mxCell(
        'Condition',
        new mxGeometry(0, 0, 80, 40),
        'rhombus;whiteSpace=wrap;html=1;fillColor=#ffffc0;strokeColor=#ff0000;'
      );
      cell.vertex = true;

      var edge1 = new mxCell(
        'no',
        new mxGeometry(0, 0, 0, 0),
        'edgeStyle=orthogonalEdgeStyle;html=1;align=left;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;'
      );
      edge1.geometry.setTerminalPoint(new mxPoint(180, 20), false);
      edge1.geometry.relative = true;
      edge1.geometry.x = -1;
      edge1.edge = true;

      cell.insertEdge(edge1, true);

      var edge2 = new mxCell(
        'yes',
        new mxGeometry(0, 0, 0, 0),
        'edgeStyle=orthogonalEdgeStyle;html=1;align=left;verticalAlign=top;endArrow=open;endSize=8;strokeColor=#ff0000;'
      );
      edge2.geometry.setTerminalPoint(new mxPoint(40, 100), false);
      edge2.geometry.relative = true;
      edge2.geometry.x = -1;
      edge2.edge = true;

      cell.insertEdge(edge2, true);

      return sb.createVertexTemplateFromCells([cell, edge1, edge2], 180, 100, 'Condition');
    }),
    this.addEntry('uml activity fork join', function () {
      var cell = new mxCell('', new mxGeometry(0, 0, 200, 10), 'shape=line;html=1;strokeWidth=6;strokeColor=#ff0000;');
      cell.vertex = true;

      var edge = new mxCell(
        '',
        new mxGeometry(0, 0, 0, 0),
        'edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(100, 80), false);
      edge.geometry.relative = true;
      edge.edge = true;

      cell.insertEdge(edge, true);

      return sb.createVertexTemplateFromCells([cell, edge], 200, 80, 'Fork/Join');
    }),
    this.createVertexTemplateEntry(
      'ellipse;html=1;shape=endState;fillColor=#000000;strokeColor=#ff0000;',
      30,
      30,
      '',
      'End',
      null,
      null,
      'uml activity state end'
    ),
    this.createVertexTemplateEntry(
      'shape=umlLifeline;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;outlineConnect=0;',
      100,
      300,
      ':Object',
      'Lifeline',
      null,
      null,
      'uml sequence participant lifeline'
    ),
    this.createVertexTemplateEntry(
      'shape=umlLifeline;participant=umlActor;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;verticalAlign=top;spacingTop=36;outlineConnect=0;',
      20,
      300,
      '',
      'Actor Lifeline',
      null,
      null,
      'uml sequence participant lifeline actor'
    ),
    this.createVertexTemplateEntry(
      'shape=umlLifeline;participant=umlBoundary;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;verticalAlign=top;spacingTop=36;outlineConnect=0;',
      50,
      300,
      '',
      'Boundary Lifeline',
      null,
      null,
      'uml sequence participant lifeline boundary'
    ),
    this.createVertexTemplateEntry(
      'shape=umlLifeline;participant=umlEntity;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;verticalAlign=top;spacingTop=36;outlineConnect=0;',
      40,
      300,
      '',
      'Entity Lifeline',
      null,
      null,
      'uml sequence participant lifeline entity'
    ),
    this.createVertexTemplateEntry(
      'shape=umlLifeline;participant=umlControl;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;verticalAlign=top;spacingTop=36;outlineConnect=0;',
      40,
      300,
      '',
      'Control Lifeline',
      null,
      null,
      'uml sequence participant lifeline control'
    ),
    this.createVertexTemplateEntry(
      'shape=umlFrame;whiteSpace=wrap;html=1;',
      300,
      200,
      'frame',
      'Frame',
      null,
      null,
      'uml sequence frame'
    ),
    this.createVertexTemplateEntry(
      'shape=umlDestroy;whiteSpace=wrap;html=1;strokeWidth=3;',
      30,
      30,
      '',
      'Destruction',
      null,
      null,
      'uml sequence destruction destroy'
    ),
    this.addEntry('uml sequence invoke invocation call activation', function () {
      var cell = new mxCell('', new mxGeometry(0, 0, 10, 80), 'html=1;points=[];perimeter=orthogonalPerimeter;');
      cell.vertex = true;

      var edge = new mxCell(
        'dispatch',
        new mxGeometry(0, 0, 0, 0),
        'html=1;verticalAlign=bottom;startArrow=oval;endArrow=block;startSize=8;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(-60, 0), true);
      edge.geometry.relative = true;
      edge.edge = true;

      cell.insertEdge(edge, false);

      return sb.createVertexTemplateFromCells([cell, edge], 10, 80, 'Found Message');
    }),
    this.addEntry('uml sequence invoke call delegation synchronous invocation activation', function () {
      var cell = new mxCell('', new mxGeometry(0, 0, 10, 80), 'html=1;points=[];perimeter=orthogonalPerimeter;');
      cell.vertex = true;

      var edge1 = new mxCell(
        'dispatch',
        new mxGeometry(0, 0, 0, 0),
        'html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;'
      );
      edge1.geometry.setTerminalPoint(new mxPoint(-70, 0), true);
      edge1.geometry.relative = true;
      edge1.edge = true;

      cell.insertEdge(edge1, false);

      var edge2 = new mxCell(
        'return',
        new mxGeometry(0, 0, 0, 0),
        'html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;exitX=0;exitY=0.95;'
      );
      edge2.geometry.setTerminalPoint(new mxPoint(-70, 76), false);
      edge2.geometry.relative = true;
      edge2.edge = true;

      cell.insertEdge(edge2, true);

      return sb.createVertexTemplateFromCells([cell, edge1, edge2], 10, 80, 'Synchronous Invocation');
    }),
    this.addEntry('uml sequence self call recursion delegation activation', function () {
      var cell = new mxCell('', new mxGeometry(0, 20, 10, 40), 'html=1;points=[];perimeter=orthogonalPerimeter;');
      cell.vertex = true;

      var edge = new mxCell(
        'self call',
        new mxGeometry(0, 0, 0, 0),
        'edgeStyle=orthogonalEdgeStyle;html=1;align=left;spacingLeft=2;endArrow=block;rounded=0;entryX=1;entryY=0;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(5, 0), true);
      edge.geometry.points = [new mxPoint(30, 0)];
      edge.geometry.relative = true;
      edge.edge = true;

      cell.insertEdge(edge, false);

      return sb.createVertexTemplateFromCells([cell, edge], 10, 60, 'Self Call');
    }),
    this.addEntry('uml sequence invoke call delegation callback activation', function () {
      // TODO: Check if more entries should be converted to compressed XML
      return sb.createVertexTemplateFromData(
        'xZRNT8MwDIZ/Ta6oaymD47rBTkiTuMAxW6wmIm0q19s6fj1OE3V0Y2iCA4dK8euP2I+riGxedUuUjX52CqzIHkU2R+conKpuDtaKNDFKZAuRpgl/In264J303qSRCDVdk5CGhJ20WwhKEFo62ChoqritxURkReNMTa2X80LkC68AmgoIkEWHpF3pamlXR7WIFwASdBeb7KXY4RIc5+KBQ/ZGkY4RYY5Egyl1zLqLmmyDXQ6Zx4n5EIf+HkB2BmAjrV3LzftPIPw4hgNn1pQ1a2tH5Cp2QK1miG7vNeu4iJe4pdeY2BtvbCQDGlAljMCQxBJotJ8rWCFYSWY3LvUdmZi68rvkkLiU6QnL1m1xAzHoBOdw61WEb88II9AW67/ydQ2wq1Cy1aAGvOrFfPh6997qDA3g+dxzv3nIL6MPU/8T+kMw8+m4QPgdfrEJNo8PSQj/+s58Ag==',
        10,
        60,
        'Callback'
      );
    }),
    this.createVertexTemplateEntry(
      'html=1;points=[];perimeter=orthogonalPerimeter;',
      10,
      80,
      '',
      'Activation',
      null,
      null,
      'uml sequence activation'
    ),

    // new 2.5.1 shapes ******************************************************************************

    this.createVertexTemplateEntry(
      'shape=partialRectangle;html=1;top=1;align=left;dashed=1;',
      200,
      20,
      'Template1 signature',
      'Template signature',
      null,
      null,
      'template signature'
    ),
    this.createVertexTemplateEntry(
      'shape=partialRectangle;html=1;top=1;align=left;dashed=1;',
      200,
      50,
      'Template parameter 1\nTemplate parameter 2',
      'Template signature',
      null,
      null,
      'template signature'
    ),
    this.createVertexTemplateEntry(
      'shape=note2;boundedLbl=1;whiteSpace=wrap;html=1;size=25;verticalAlign=top;align=center;',
      120,
      60,
      'Comment1 body',
      'Note',
      null,
      null,
      'uml note'
    ),

    this.addEntry('uml sequence self call recursion delegation activation', function () {
      var cell = new mxCell(
        'Constraint1 specification',
        new mxGeometry(0, 0, 160, 60),
        'shape=note2;boundedLbl=1;whiteSpace=wrap;html=1;size=25;verticalAlign=top;align=center;'
      );
      cell.vertex = true;
      var label = new mxCell(
        '<<keyword>>',
        new mxGeometry(0, 0, cell.geometry.width, 25),
        'resizeWidth=1;part=1;strokeColor=none;fillColor=none;align=left;spacingLeft=5;'
      );
      label.geometry.relative = true;
      label.vertex = true;
      cell.insert(label);

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Note');
    }),

    this.addEntry(dt + 'classifier', function () {
      var cell1 = new mxCell(
        '&lt;&lt;keyword&gt;&gt;<br><b>Classifier1</b><br>{abstract}',
        new mxGeometry(0, 0, 140, 183),
        'swimlane;fontStyle=0;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=55;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;marginBottom=0;html=1;'
      );
      cell1.vertex = true;
      var field1 = new mxCell(
        'attributes',
        new mxGeometry(0, 0, 140, 20),
        'text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field1.vertex = true;
      cell1.insert(field1);
      var field2 = new mxCell(
        'attribute1',
        new mxGeometry(0, 0, 140, 20),
        'text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field2.vertex = true;
      cell1.insert(field2);
      var field3 = new mxCell(
        'inherited attribute2',
        new mxGeometry(0, 0, 140, 20),
        'text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontColor=#808080;'
      );
      field3.vertex = true;
      cell1.insert(field3);
      var field4 = new mxCell(
        '...',
        new mxGeometry(0, 0, 140, 20),
        'text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field4.vertex = true;
      cell1.insert(field4);
      cell1.insert(divider.clone());
      var field5 = new mxCell(
        'operations',
        new mxGeometry(0, 0, 140, 20),
        'text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field5.vertex = true;
      cell1.insert(field5);
      var field6 = new mxCell(
        'operation1',
        new mxGeometry(0, 0, 140, 20),
        'text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field6.vertex = true;
      cell1.insert(field6);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Classifier');
    }),

    this.createVertexTemplateEntry(
      'shape=process;fixedSize=1;size=5;fontStyle=1;',
      140,
      40,
      'Classifier1',
      'Classifier',
      null,
      null,
      'classifier'
    ),

    this.addEntry(dt + 'classifier', function () {
      var cell1 = new mxCell(
        'Classifier1',
        new mxGeometry(0, 0, 140, 183),
        'swimlane;fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;marginBottom=0;html=1;'
      );
      cell1.vertex = true;
      var field1 = new mxCell(
        'internal structure',
        new mxGeometry(0, 0, 140, 30),
        'html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;spacingLeft=4;spacingRight=4;rotatable=0;points=[[0,0.5],[1,0.5]];resizeWidth=1;'
      );
      field1.vertex = true;
      cell1.insert(field1);

      var cell2 = new mxCell('', new mxGeometry(0, 0, 140, 140), 'strokeColor=none;fillColor=none;');
      cell2.vertex = true;
      cell1.insert(cell2);

      var field2 = new mxCell(
        'property1',
        new mxGeometry(0, 0, 100, 30),
        'html=1;align=center;verticalAlign=middle;rotatable=0;'
      );
      field2.geometry.relative = true;
      field2.geometry.offset = new mxPoint(20, 20);
      field2.vertex = true;
      cell2.insert(field2);

      var field3 = new mxCell(
        'property2',
        new mxGeometry(0, 0, 100, 30),
        'html=1;align=center;verticalAlign=middle;rotatable=0;'
      );
      field3.geometry.relative = true;
      field3.geometry.offset = new mxPoint(20, 90);
      field3.vertex = true;
      cell2.insert(field3);

      var assoc1 = new mxCell(
        'connector1',
        new mxGeometry(0, 0, 0, 0),
        'edgeStyle=none;endArrow=none;verticalAlign=middle;labelBackgroundColor=none;endSize=12;html=1;align=left;endFill=0;exitX=0.15;exitY=1;entryX=0.15;entryY=0;spacingLeft=4;'
      );
      assoc1.geometry.relative = true;
      assoc1.edge = true;
      field2.insertEdge(assoc1, true);
      field3.insertEdge(assoc1, false);
      cell2.insert(assoc1);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Classifier');
    }),

    this.createVertexTemplateEntry('fontStyle=1;', 140, 30, 'Association1', 'Association', null, null, 'association'),

    this.addEntry(dt + 'classifier', function () {
      var cell1 = new mxCell(
        'Instance1',
        new mxGeometry(0, 0, 140, 138),
        'swimlane;fontStyle=4;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=30;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;marginBottom=0;html=1;'
      );
      cell1.vertex = true;
      var field1 = new mxCell(
        'slot1',
        new mxGeometry(0, 0, 140, 30),
        'html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;rotatable=0;points=[[0,0.5],[1,0.5]];resizeWidth=1;'
      );
      field1.vertex = true;
      cell1.insert(field1);

      cell1.insert(divider.clone());

      var field2 = new mxCell(
        'internal structure',
        new mxGeometry(0, 0, 140, 20),
        'html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;spacingLeft=4;spacingRight=4;rotatable=0;points=[[0,0.5],[1,0.5]];resizeWidth=1;'
      );
      field2.vertex = true;
      cell1.insert(field2);

      var cell2 = new mxCell('', new mxGeometry(0, 0, 140, 50), 'strokeColor=none;fillColor=none;');
      cell2.vertex = true;
      cell1.insert(cell2);

      var field3 = new mxCell(
        'instance2',
        new mxGeometry(0, 0, 80, 30),
        'html=1;align=center;verticalAlign=middle;rotatable=0;'
      );
      field3.geometry.relative = true;
      field3.geometry.offset = new mxPoint(30, 10);
      field3.vertex = true;
      cell2.insert(field3);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Classifier');
    }),

    this.createVertexTemplateEntry('fontStyle=0;', 120, 40, 'Instance1 value', 'Instance', null, null, 'instance'),

    this.addEntry(dt + 'classifier', function () {
      var cell1 = new mxCell(
        '&lt;&lt;enumeration&gt;&gt;<br><b>Enum1</b>',
        new mxGeometry(0, 0, 140, 70),
        'swimlane;fontStyle=0;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=40;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;marginBottom=0;html=1;'
      );
      cell1.vertex = true;
      var field1 = new mxCell(
        'literal1',
        new mxGeometry(0, 0, 140, 30),
        'text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field1.vertex = true;
      cell1.insert(field1);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Classifier');
    }),

    this.addEntry(dt + 'classifier', function () {
      var cell1 = new mxCell('0..1', new mxGeometry(0, 0, 120, 50), 'align=right;verticalAlign=top;spacingRight=2;');
      cell1.vertex = true;
      var field1 = new mxCell(
        'Property1',
        new mxGeometry(0, 1, 120, 30),
        'text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;resizeWidth=1;'
      );
      field1.geometry.relative = true;
      field1.geometry.offset = new mxPoint(0, -30);
      field1.vertex = true;

      cell1.insert(field1);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Classifier');
    }),

    this.createVertexTemplateEntry('fontStyle=0;dashed=1;', 140, 30, 'Property1', 'Property', null, null, 'property'),
    this.createVertexTemplateEntry(
      'fontStyle=0;labelPosition=right;verticalLabelPosition=middle;align=left;verticalAlign=middle;spacingLeft=2;',
      30,
      30,
      'port1',
      'Port',
      null,
      null,
      'port'
    ),

    this.addEntry(dt + 'component', function () {
      var cell1 = new mxCell(
        '',
        new mxGeometry(0, 0, 140, 200),
        'fontStyle=1;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;marginBottom=0;'
      );
      cell1.vertex = true;

      var cell2 = new mxCell(
        'Component',
        new mxGeometry(0, 0, 140, 40),
        'html=1;align=left;spacingLeft=4;verticalAlign=top;strokeColor=none;fillColor=none;'
      );
      cell2.vertex = true;
      cell1.insert(cell2);

      var symbol = new mxCell('', new mxGeometry(1, 0, 16, 20), 'shape=module;jettyWidth=10;jettyHeight=4;');
      symbol.vertex = true;
      symbol.geometry.relative = true;
      symbol.geometry.offset = new mxPoint(-25, 9);
      cell2.insert(symbol);

      cell1.insert(divider.clone());

      var cell3 = new mxCell(
        'provided interfaces',
        new mxGeometry(0, 0, 140, 25),
        'html=1;align=center;spacingLeft=4;verticalAlign=top;strokeColor=none;fillColor=none;'
      );
      cell3.vertex = true;
      cell1.insert(cell3);

      var cell4 = new mxCell(
        'Interface1',
        new mxGeometry(0, 0, 140, 25),
        'html=1;align=left;spacingLeft=4;verticalAlign=top;strokeColor=none;fillColor=none;'
      );
      cell4.vertex = true;
      cell1.insert(cell4);

      cell1.insert(divider.clone());

      var cell5 = new mxCell(
        'required interfaces',
        new mxGeometry(0, 0, 140, 25),
        'html=1;align=center;spacingLeft=4;verticalAlign=top;strokeColor=none;fillColor=none;'
      );
      cell5.vertex = true;
      cell1.insert(cell5);

      var cell6 = new mxCell(
        'Interface2',
        new mxGeometry(0, 0, 140, 30),
        'html=1;align=left;spacingLeft=4;verticalAlign=top;strokeColor=none;fillColor=none;'
      );
      cell6.vertex = true;
      cell1.insert(cell6);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Component');
    }),

    this.addEntry(dt + 'classifier', function () {
      var cell1 = new mxCell(
        '',
        new mxGeometry(0, 0, 270, 230),
        'shape=ellipse;container=1;horizontal=1;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;html=1;dashed=1;collapsible=0;'
      );
      cell1.vertex = true;

      var field1 = new mxCell(
        'Collaboration1',
        new mxGeometry(0, 0, 270, 30),
        'html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;spacingLeft=4;spacingRight=4;rotatable=0;points=[[0,0.5],[1,0.5]];resizeWidth=1;'
      );
      field1.vertex = true;
      cell1.insert(field1);

      var divider1 = new mxCell(
        '',
        new mxGeometry(0.145, 0, 192, 8),
        'line;strokeWidth=1;fillColor=none;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;dashed=1;resizeWidth=1;'
      );
      divider1.geometry.relative = true;
      divider1.geometry.offset = new mxPoint(0, 30);
      divider1.vertex = true;
      cell1.insert(divider1);

      var field2 = new mxCell(
        'Classifier1',
        new mxGeometry(0, 0, 100, 30),
        'html=1;align=center;verticalAlign=middle;rotatable=0;'
      );
      field2.geometry.relative = true;
      field2.geometry.offset = new mxPoint(85, 50);
      field2.vertex = true;
      cell1.insert(field2);

      var field3 = new mxCell(
        'Collaboration use 1',
        new mxGeometry(0, 0, 140, 30),
        'shape=ellipse;html=1;align=center;verticalAlign=middle;rotatable=0;dashed=1;'
      );
      field3.geometry.relative = true;
      field3.geometry.offset = new mxPoint(65, 110);
      field3.vertex = true;
      cell1.insert(field3);

      var assoc1 = new mxCell(
        'property1',
        new mxGeometry(0, 0, 0, 0),
        'edgeStyle=none;endArrow=none;verticalAlign=middle;labelBackgroundColor=none;endSize=12;html=1;align=left;endFill=0;spacingLeft=4;'
      );
      assoc1.geometry.relative = true;
      assoc1.edge = true;
      field2.insertEdge(assoc1, true);
      field3.insertEdge(assoc1, false);
      cell1.insert(assoc1);

      var field4 = new mxCell(
        'Classifier2',
        new mxGeometry(0, 0, 100, 30),
        'html=1;align=center;verticalAlign=middle;rotatable=0;'
      );
      field4.geometry.relative = true;
      field4.geometry.offset = new mxPoint(85, 170);
      field4.vertex = true;
      cell1.insert(field4);

      var assoc2 = new mxCell(
        'property1',
        new mxGeometry(0, 0, 0, 0),
        'edgeStyle=none;endArrow=none;verticalAlign=middle;labelBackgroundColor=none;endSize=12;html=1;align=left;endFill=0;spacingLeft=4;'
      );
      assoc2.geometry.relative = true;
      assoc2.edge = true;
      field3.insertEdge(assoc2, true);
      field4.insertEdge(assoc2, false);
      cell1.insert(assoc2);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Classifier');
    }),

    this.createVertexTemplateEntry(
      'shape=folder;fontStyle=1;tabWidth=80;tabHeight=30;tabPosition=left;html=1;boundedLbl=1;',
      150,
      80,
      'Package1',
      'Package',
      null,
      null,
      dt + 'package'
    ),

    this.addEntry(dt + 'package', function () {
      var cell1 = new mxCell(
        'Package1',
        new mxGeometry(0, 0, 150, 100),
        'shape=folder;fontStyle=1;tabWidth=110;tabHeight=30;tabPosition=left;html=1;boundedLbl=1;labelInHeader=1;'
      );
      cell1.vertex = true;

      var field1 = new mxCell('Packaged element1', new mxGeometry(0, 0, 110, 30), 'html=1;');
      field1.geometry.relative = true;
      field1.geometry.offset = new mxPoint(20, 50);
      field1.vertex = true;
      cell1.insert(field1);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Classifier');
    }),

    this.addEntry(dt + 'package', function () {
      var cell1 = new mxCell(
        'Model1',
        new mxGeometry(0, 0, 150, 80),
        'shape=folder;fontStyle=1;tabWidth=110;tabHeight=30;tabPosition=left;html=1;boundedLbl=1;folderSymbol=triangle;'
      );
      cell1.vertex = true;

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Package');
    }),

    this.addEntry(dt + 'stereotype', function () {
      var cell1 = new mxCell(
        '',
        new mxGeometry(0, 0, 160, 75),
        'shape=note2;size=25;childLayout=stackLayout;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;marginBottom=0;html=1;container=1;'
      );
      cell1.vertex = true;
      var field1 = new mxCell(
        '&lt;&lt;stereotype1&gt;&gt;',
        new mxGeometry(0, 0, 160, 25),
        'text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field1.vertex = true;
      cell1.insert(field1);
      var field2 = new mxCell(
        'stereotype property 1',
        new mxGeometry(0, 0, 160, 25),
        'text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field2.vertex = true;
      cell1.insert(field2);
      var field3 = new mxCell(
        'stereotype property 2',
        new mxGeometry(0, 0, 160, 25),
        'text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field3.vertex = true;
      cell1.insert(field3);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Stereotype');
    }),

    this.addEntry(dt + 'class', function () {
      var cell1 = new mxCell(
        'Class1',
        new mxGeometry(0, 0, 140, 79),
        'swimlane;fontStyle=1;align=center;verticalAlign=middle;childLayout=stackLayout;horizontal=1;startSize=29;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;marginBottom=0;html=1;'
      );
      cell1.vertex = true;
      var field1 = new mxCell(
        '&lt;&lt;stereotype1&gt;&gt;',
        new mxGeometry(0, 0, 140, 25),
        'text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field1.vertex = true;
      cell1.insert(field1);
      var field2 = new mxCell(
        'stereotype property 1',
        new mxGeometry(0, 0, 140, 25),
        'text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;'
      );
      field2.vertex = true;
      cell1.insert(field2);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'Class');
    }),

    this.createVertexTemplateEntry(
      'text;html=1;align=center;',
      200,
      25,
      '&lt;&lt;stereotype1, stereotype2...&gt;&gt;',
      'Label',
      null,
      null,
      dt + 'label'
    ),
    this.createVertexTemplateEntry('ellipse;', 50, 25, 'icon', 'Icon', null, null, dt + 'icon'),

    this.addEntry(dt + 'region', function () {
      var cell1 = new mxCell(
        '',
        new mxGeometry(60, 0, 10, 100),
        'line;strokeWidth=1;direction=south;html=1;dashed=1;dashPattern=20 20;'
      );
      cell1.vertex = true;

      var cell2 = new mxCell('Region 1', new mxGeometry(0, 40, 60, 20), 'text;align=right;');
      cell2.vertex = true;

      var cell3 = new mxCell('Region 2', new mxGeometry(70, 40, 60, 20), 'text;align=left;');
      cell3.vertex = true;

      return sb.createVertexTemplateFromCells([cell1, cell2, cell3], 130, cell1.geometry.height, 'Region');
    }),

    this.addEntry(dt + 'State', function () {
      var cell1 = new mxCell(
        'State1<br>[invariant1]<br>&lt;&lt;extended/final&gt;&gt;',
        new mxGeometry(0, 0, 140, 176),
        'swimlane;fontStyle=4;align=center;verticalAlign=top;childLayout=stackLayout;horizontal=1;startSize=60;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=0;marginBottom=0;html=1;rounded=1;absoluteArcSize=1;arcSize=50;'
      );
      cell1.vertex = true;

      var field1 = new mxCell(
        '',
        new mxGeometry(0, 0, 140, 50),
        'fillColor=none;strokeColor=none;container=1;collapsible=0;'
      );
      field1.vertex = true;
      cell1.insert(field1);

      var field2 = new mxCell(
        'State2',
        new mxGeometry(30, 10, 80, 30),
        'html=1;align=center;verticalAlign=middle;rounded=1;absoluteArcSize=1;arcSize=10;'
      );
      field2.vertex = true;
      field1.insert(field2);

      cell1.insert(divider.clone());

      var field3 = new mxCell(
        'behavior1',
        new mxGeometry(0, 0, 140, 25),
        'fillColor=none;strokeColor=none;align=left;verticalAlign=middle;spacingLeft=5;'
      );
      field3.vertex = true;
      cell1.insert(field3);

      cell1.insert(divider.clone());

      var field4 = new mxCell(
        'transition1',
        new mxGeometry(0, 0, 140, 25),
        'fillColor=none;strokeColor=none;align=left;verticalAlign=middle;spacingLeft=5;'
      );
      field4.vertex = true;
      cell1.insert(field4);

      return sb.createVertexTemplateFromCells([cell1], cell1.geometry.width, cell1.geometry.height, 'State');
    }),

    this.createVertexTemplateEntry(
      'html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=1;',
      140,
      40,
      'State1',
      'State',
      null,
      null,
      dt + 'state'
    ),

    this.createVertexTemplateEntry(
      'html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;',
      140,
      40,
      'State',
      'State',
      null,
      null,
      dt + 'state'
    ),

    this.createVertexTemplateEntry(
      'shape=folder;align=center;verticalAlign=middle;fontStyle=0;tabWidth=100;tabHeight=30;tabPosition=left;html=1;boundedLbl=1;labelInHeader=1;rounded=1;absoluteArcSize=1;arcSize=10;',
      140,
      90,
      'State1',
      'State',
      null,
      null,
      dt + 'state'
    ),

    this.createVertexTemplateEntry(
      'html=1;align=center;verticalAlign=top;rounded=1;absoluteArcSize=1;arcSize=10;dashed=0;',
      140,
      40,
      'State1, State2, ...',
      'State',
      null,
      null,
      dt + 'state'
    ),

    this.createVertexTemplateEntry(
      'shape=umlState;rounded=1;verticalAlign=top;spacingTop=5;umlStateSymbol=collapseState;absoluteArcSize=1;arcSize=10;',
      140,
      60,
      'State1',
      'State',
      null,
      null,
      dt + 'state'
    ),

    this.addEntry(dt + 'State', function () {
      var cell1 = new mxCell(
        'State1',
        new mxGeometry(40, 0, 140, 50),
        'shape=umlState;rounded=1;verticalAlign=middle;align=center;absoluteArcSize=1;arcSize=10;umlStateConnection=connPointRefEntry;boundedLbl=1;'
      );
      cell1.vertex = true;

      var field1 = new mxCell('Entry1', new mxGeometry(0, 40, 50, 20), 'text;verticalAlign=middle;align=center;');
      field1.vertex = true;
      cell1.insert(field1);

      return sb.createVertexTemplateFromCells([cell1, field1], 180, 60, 'State');
    }),

    this.addEntry(dt + 'State', function () {
      var cell1 = new mxCell(
        'State1',
        new mxGeometry(40, 0, 140, 50),
        'shape=umlState;rounded=1;verticalAlign=middle;spacingTop=0;absoluteArcSize=1;arcSize=10;umlStateConnection=connPointRefExit;boundedLbl=1;'
      );
      cell1.vertex = true;

      var field1 = new mxCell('Exit1', new mxGeometry(0, 40, 50, 20), 'text;verticalAlign=middle;align=center;');
      field1.vertex = true;
      cell1.insert(field1);

      return sb.createVertexTemplateFromCells([cell1, field1], 180, 60, 'State');
    }),

    this.createVertexTemplateEntry(
      'ellipse;fillColor=#000000;strokeColor=none;',
      30,
      30,
      '',
      'Initial state',
      null,
      null,
      dt + 'initial state'
    ),

    this.createVertexTemplateEntry(
      'ellipse;html=1;shape=endState;fillColor=#000000;strokeColor=#000000;',
      30,
      30,
      '',
      'Final state',
      null,
      null,
      dt + 'final state'
    ),

    this.createVertexTemplateEntry(
      'ellipse;fillColor=#ffffff;strokeColor=#000000;',
      30,
      30,
      'H',
      'Shallow History',
      null,
      null,
      dt + 'shallow history'
    ),

    this.createVertexTemplateEntry(
      'ellipse;fillColor=#ffffff;strokeColor=#000000;',
      30,
      30,
      'H*',
      'Deep History',
      null,
      null,
      dt + 'deep history'
    ),

    this.createVertexTemplateEntry(
      'ellipse;fillColor=#ffffff;strokeColor=#000000;',
      30,
      30,
      '',
      'Entry Point',
      null,
      null,
      dt + 'entry point'
    ),

    this.createVertexTemplateEntry(
      'shape=sumEllipse;perimeter=ellipsePerimeter;whiteSpace=wrap;html=1;backgroundOutline=1;',
      30,
      30,
      '',
      'Exit Point',
      null,
      null,
      dt + 'exit point'
    ),

    this.createVertexTemplateEntry(
      'ellipse;fillColor=#000000;strokeColor=none;',
      20,
      20,
      '',
      'Junction',
      null,
      null,
      dt + 'junction'
    ),

    this.createVertexTemplateEntry('rhombus;', 30, 30, '', 'Choice', null, null, dt + 'choice'),

    this.createVertexTemplateEntry('shape=umlDestroy;', 30, 30, '', 'Terminate', null, null, dt + 'terminate'),

    this.createVertexTemplateEntry(
      'html=1;points=[];perimeter=orthogonalPerimeter;fillColor=#000000;strokeColor=none;',
      5,
      80,
      '',
      'Join/Fork',
      null,
      null,
      dt + 'join fork'
    ),

    this.createVertexTemplateEntry(
      'text;align=center;verticalAlign=middle;dashed=0;fillColor=#ffffff;strokeColor=#000000;',
      140,
      40,
      'OpaqueAction1 spec.',
      'Opaque Action',
      null,
      null,
      dt + 'opaque action'
    ),

    // end of new shapes ******************************************************************************

    this.createEdgeTemplateEntry(
      'html=1;verticalAlign=bottom;startArrow=oval;startFill=1;endArrow=block;startSize=8;',
      60,
      0,
      'dispatch',
      'Found Message 1',
      null,
      'uml sequence message call invoke dispatch'
    ),
    this.createEdgeTemplateEntry(
      'html=1;verticalAlign=bottom;startArrow=circle;startFill=1;endArrow=open;startSize=6;endSize=8;',
      80,
      0,
      'dispatch',
      'Found Message 2',
      null,
      'uml sequence message call invoke dispatch'
    ),
    this.createEdgeTemplateEntry(
      'html=1;verticalAlign=bottom;endArrow=block;',
      80,
      0,
      'dispatch',
      'Message',
      null,
      'uml sequence message call invoke dispatch'
    ),
    this.addEntry('uml sequence return message', function () {
      var edge = new mxCell(
        'return',
        new mxGeometry(0, 0, 0, 0),
        'html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(80, 0), true);
      edge.geometry.setTerminalPoint(new mxPoint(0, 0), false);
      edge.geometry.relative = true;
      edge.edge = true;

      return sb.createEdgeTemplateFromCells([edge], 80, 0, 'Return');
    }),
    this.addEntry('uml relation', function () {
      var edge = new mxCell(
        'name',
        new mxGeometry(0, 0, 0, 0),
        'endArrow=block;endFill=1;html=1;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=top;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(0, 0), true);
      edge.geometry.setTerminalPoint(new mxPoint(160, 0), false);
      edge.geometry.relative = true;
      edge.geometry.x = -1;
      edge.edge = true;

      var cell = new mxCell(
        '1',
        new mxGeometry(-1, 0, 0, 0),
        'edgeLabel;resizable=0;html=1;align=left;verticalAlign=bottom;'
      );
      cell.geometry.relative = true;
      cell.setConnectable(false);
      cell.vertex = true;
      edge.insert(cell);

      return sb.createEdgeTemplateFromCells([edge], 160, 0, 'Relation 1');
    }),
    this.addEntry('uml association', function () {
      var edge = new mxCell('', new mxGeometry(0, 0, 0, 0), 'endArrow=none;html=1;edgeStyle=orthogonalEdgeStyle;');
      edge.geometry.setTerminalPoint(new mxPoint(0, 0), true);
      edge.geometry.setTerminalPoint(new mxPoint(160, 0), false);
      edge.geometry.relative = true;
      edge.edge = true;

      var cell1 = new mxCell(
        'parent',
        new mxGeometry(-1, 0, 0, 0),
        'edgeLabel;resizable=0;html=1;align=left;verticalAlign=bottom;'
      );
      cell1.geometry.relative = true;
      cell1.setConnectable(false);
      cell1.vertex = true;
      edge.insert(cell1);

      var cell2 = new mxCell(
        'child',
        new mxGeometry(1, 0, 0, 0),
        'edgeLabel;resizable=0;html=1;align=right;verticalAlign=bottom;'
      );
      cell2.geometry.relative = true;
      cell2.setConnectable(false);
      cell2.vertex = true;
      edge.insert(cell2);

      return sb.createEdgeTemplateFromCells([edge], 160, 0, 'Association 1');
    }),
    this.addEntry('uml aggregation', function () {
      var edge = new mxCell(
        '1',
        new mxGeometry(0, 0, 0, 0),
        'endArrow=open;html=1;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(0, 0), true);
      edge.geometry.setTerminalPoint(new mxPoint(160, 0), false);
      edge.geometry.relative = true;
      edge.geometry.x = -1;
      edge.geometry.y = 3;
      edge.edge = true;

      return sb.createEdgeTemplateFromCells([edge], 160, 0, 'Aggregation 1');
    }),
    this.addEntry('uml composition', function () {
      var edge = new mxCell(
        '1',
        new mxGeometry(0, 0, 0, 0),
        'endArrow=open;html=1;endSize=12;startArrow=diamondThin;startSize=14;startFill=1;edgeStyle=orthogonalEdgeStyle;align=left;verticalAlign=bottom;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(0, 0), true);
      edge.geometry.setTerminalPoint(new mxPoint(160, 0), false);
      edge.geometry.relative = true;
      edge.geometry.x = -1;
      edge.geometry.y = 3;
      edge.edge = true;

      return sb.createEdgeTemplateFromCells([edge], 160, 0, 'Composition 1');
    }),
    this.addEntry('uml relation', function () {
      var edge = new mxCell(
        'Relation',
        new mxGeometry(0, 0, 0, 0),
        'endArrow=open;html=1;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(0, 0), true);
      edge.geometry.setTerminalPoint(new mxPoint(160, 0), false);
      edge.geometry.relative = true;
      edge.edge = true;

      var cell1 = new mxCell(
        '0..n',
        new mxGeometry(-1, 0, 0, 0),
        'edgeLabel;resizable=0;html=1;align=left;verticalAlign=top;'
      );
      cell1.geometry.relative = true;
      cell1.setConnectable(false);
      cell1.vertex = true;
      edge.insert(cell1);

      var cell2 = new mxCell(
        '1',
        new mxGeometry(1, 0, 0, 0),
        'edgeLabel;resizable=0;html=1;align=right;verticalAlign=top;'
      );
      cell2.geometry.relative = true;
      cell2.setConnectable(false);
      cell2.vertex = true;
      edge.insert(cell2);

      return sb.createEdgeTemplateFromCells([edge], 160, 0, 'Relation 2');
    }),
    this.createEdgeTemplateEntry(
      'endArrow=open;endSize=12;dashed=1;html=1;',
      160,
      0,
      'Use',
      'Dependency',
      null,
      'uml dependency use'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=block;endSize=16;endFill=0;html=1;',
      160,
      0,
      'Extends',
      'Generalization',
      null,
      'uml generalization extend'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=block;startArrow=block;endFill=1;startFill=1;html=1;',
      160,
      0,
      '',
      'Association 2',
      null,
      'uml association'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=open;startArrow=circlePlus;endFill=0;startFill=0;endSize=8;html=1;',
      160,
      0,
      '',
      'Inner Class',
      null,
      'uml inner class'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=open;startArrow=cross;endFill=0;startFill=0;endSize=8;startSize=10;html=1;',
      160,
      0,
      '',
      'Terminate',
      null,
      'uml terminate'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=block;dashed=1;endFill=0;endSize=12;html=1;',
      160,
      0,
      '',
      'Implementation',
      null,
      'uml realization implementation'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=diamondThin;endFill=0;endSize=24;html=1;',
      160,
      0,
      '',
      'Aggregation 2',
      null,
      'uml aggregation'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=diamondThin;endFill=1;endSize=24;html=1;',
      160,
      0,
      '',
      'Composition 2',
      null,
      'uml composition'
    ),
    this.createEdgeTemplateEntry(
      'endArrow=open;endFill=1;endSize=12;html=1;',
      160,
      0,
      '',
      'Association 3',
      null,
      'uml association'
    ),
  ];

  this.addPaletteFunctions('uml', mxResources.get('uml'), expand || false, fns);
  this.setCurrentSearchEntryLibrary();
};

/**
 * Adds the BPMN library to the sidebar.
 */
Sidebar.prototype.addBpmnPalette = function (dir, expand) {
  // Avoids having to bind all functions to "this"
  var sb = this;
  this.setCurrentSearchEntryLibrary('bpmn');

  var fns = [
    this.createVertexTemplateEntry(
      'shape=ext;rounded=1;html=1;whiteSpace=wrap;',
      120,
      80,
      'Task',
      'Process',
      null,
      null,
      'bpmn task process'
    ),
    this.createVertexTemplateEntry(
      'shape=ext;rounded=1;html=1;whiteSpace=wrap;double=1;',
      120,
      80,
      'Transaction',
      'Transaction',
      null,
      null,
      'bpmn transaction'
    ),
    this.createVertexTemplateEntry(
      'shape=ext;rounded=1;html=1;whiteSpace=wrap;dashed=1;dashPattern=1 4;',
      120,
      80,
      'Event\nSub-Process',
      'Event Sub-Process',
      null,
      null,
      'bpmn event subprocess sub process sub-process'
    ),
    this.createVertexTemplateEntry(
      'shape=ext;rounded=1;html=1;whiteSpace=wrap;strokeWidth=3;',
      120,
      80,
      'Call Activity',
      'Call Activity',
      null,
      null,
      'bpmn call activity'
    ),
    this.addEntry('bpmn subprocess sub process sub-process', function () {
      var cell = new mxCell(
        'Sub-Process',
        new mxGeometry(0, 0, 120, 80),
        'html=1;whiteSpace=wrap;rounded=1;dropTarget=0;'
      );
      cell.vertex = true;

      var cell1 = new mxCell('', new mxGeometry(0.5, 1, 14, 14), 'html=1;shape=plus;outlineConnect=0;');
      cell1.vertex = true;
      cell1.geometry.relative = true;
      cell1.geometry.offset = new mxPoint(-7, -14);
      cell.insert(cell1);

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Sub-Process');
    }),
    this.addEntry(
      this.getTagsForStencil('mxgraph.bpmn', 'loop', 'subprocess sub process sub-process looped').join(' '),
      function () {
        var cell = new mxCell(
          'Looped\nSub-Process',
          new mxGeometry(0, 0, 120, 80),
          'html=1;whiteSpace=wrap;rounded=1;dropTarget=0;'
        );
        cell.vertex = true;

        var cell1 = new mxCell('', new mxGeometry(0.5, 1, 14, 14), 'html=1;shape=mxgraph.bpmn.loop;outlineConnect=0;');
        cell1.vertex = true;
        cell1.geometry.relative = true;
        cell1.geometry.offset = new mxPoint(-15, -14);
        cell.insert(cell1);

        var cell2 = new mxCell('', new mxGeometry(0.5, 1, 14, 14), 'html=1;shape=plus;');
        cell2.vertex = true;
        cell2.geometry.relative = true;
        cell2.geometry.offset = new mxPoint(1, -14);
        cell.insert(cell2);

        return sb.createVertexTemplateFromCells(
          [cell],
          cell.geometry.width,
          cell.geometry.height,
          'Looped Sub-Process'
        );
      }
    ),
    this.addEntry('bpmn receive task', function () {
      var cell = new mxCell('Receive', new mxGeometry(0, 0, 120, 80), 'html=1;whiteSpace=wrap;rounded=1;dropTarget=0;');
      cell.vertex = true;

      var cell1 = new mxCell('', new mxGeometry(0, 0, 20, 14), 'html=1;shape=message;outlineConnect=0;');
      cell1.vertex = true;
      cell1.geometry.relative = true;
      cell1.geometry.offset = new mxPoint(7, 7);
      cell.insert(cell1);

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Receive Task');
    }),
    this.addEntry(this.getTagsForStencil('mxgraph.bpmn', 'user_task').join(' '), function () {
      var cell = new mxCell('User', new mxGeometry(0, 0, 120, 80), 'html=1;whiteSpace=wrap;rounded=1;dropTarget=0;');
      cell.vertex = true;

      var cell1 = new mxCell('', new mxGeometry(0, 0, 14, 14), 'html=1;shape=mxgraph.bpmn.user_task;outlineConnect=0;');
      cell1.vertex = true;
      cell1.geometry.relative = true;
      cell1.geometry.offset = new mxPoint(7, 7);
      cell.insert(cell1);

      var cell2 = new mxCell('', new mxGeometry(0.5, 1, 14, 14), 'html=1;shape=plus;outlineConnect=0;');
      cell2.vertex = true;
      cell2.geometry.relative = true;
      cell2.geometry.offset = new mxPoint(-7, -14);
      cell.insert(cell2);

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'User Task');
    }),
    this.addEntry(this.getTagsForStencil('mxgraph.bpmn', 'timer_start', 'attached').join(' '), function () {
      var cell = new mxCell('Process', new mxGeometry(0, 0, 120, 80), 'html=1;whiteSpace=wrap;rounded=1;dropTarget=0;');
      cell.vertex = true;

      var cell1 = new mxCell(
        '',
        new mxGeometry(1, 1, 30, 30),
        'shape=mxgraph.bpmn.timer_start;perimeter=ellipsePerimeter;html=1;verticalLabelPosition=bottom;verticalAlign=top;outlineConnect=0;'
      );
      cell1.vertex = true;
      cell1.geometry.relative = true;
      cell1.geometry.offset = new mxPoint(-40, -15);
      cell.insert(cell1);

      return sb.createVertexTemplateFromCells([cell], 120, 95, 'Attached Timer Event 1');
    }),
    this.addEntry(this.getTagsForStencil('mxgraph.bpmn', 'timer_start', 'attached').join(' '), function () {
      var cell = new mxCell('Process', new mxGeometry(0, 0, 120, 80), 'html=1;whiteSpace=wrap;rounded=1;dropTarget=0;');
      cell.vertex = true;

      var cell1 = new mxCell(
        '',
        new mxGeometry(1, 0, 30, 30),
        'shape=mxgraph.bpmn.timer_start;perimeter=ellipsePerimeter;html=1;labelPosition=right;align=left;outlineConnect=0;'
      );
      cell1.vertex = true;
      cell1.geometry.relative = true;
      cell1.geometry.offset = new mxPoint(-15, 10);
      cell.insert(cell1);

      return sb.createVertexTemplateFromCells([cell], 135, 80, 'Attached Timer Event 2');
    }),
    this.createVertexTemplateEntry(
      'swimlane;html=1;horizontal=0;startSize=20;',
      320,
      240,
      'Pool',
      'Pool',
      null,
      null,
      'bpmn pool'
    ),
    this.createVertexTemplateEntry(
      'swimlane;html=1;horizontal=0;swimlaneLine=0;',
      300,
      120,
      'Lane',
      'Lane',
      null,
      null,
      'bpmn lane'
    ),
    this.createVertexTemplateEntry(
      'shape=hexagon;html=1;whiteSpace=wrap;perimeter=hexagonPerimeter;rounded=0;',
      60,
      50,
      '',
      'Conversation',
      null,
      null,
      'bpmn conversation'
    ),
    this.createVertexTemplateEntry(
      'shape=hexagon;html=1;whiteSpace=wrap;perimeter=hexagonPerimeter;strokeWidth=4;rounded=0;',
      60,
      50,
      '',
      'Call Conversation',
      null,
      null,
      'bpmn call conversation'
    ),
    this.addEntry('bpmn subconversation sub conversation sub-conversation', function () {
      var cell = new mxCell(
        '',
        new mxGeometry(0, 0, 60, 50),
        'shape=hexagon;whiteSpace=wrap;html=1;perimeter=hexagonPerimeter;rounded=0;dropTarget=0;'
      );
      cell.vertex = true;

      var cell1 = new mxCell('', new mxGeometry(0.5, 1, 14, 14), 'html=1;shape=plus;');
      cell1.vertex = true;
      cell1.geometry.relative = true;
      cell1.geometry.offset = new mxPoint(-7, -14);
      cell.insert(cell1);

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Sub-Conversation');
    }),
    this.addEntry('bpmn data object', function () {
      var cell = new mxCell(
        '',
        new mxGeometry(0, 0, 40, 60),
        'shape=note;whiteSpace=wrap;size=16;html=1;dropTarget=0;'
      );
      cell.vertex = true;

      var cell1 = new mxCell(
        '',
        new mxGeometry(0, 0, 14, 14),
        'html=1;shape=singleArrow;arrowWidth=0.4;arrowSize=0.4;outlineConnect=0;'
      );
      cell1.vertex = true;
      cell1.geometry.relative = true;
      cell1.geometry.offset = new mxPoint(2, 2);
      cell.insert(cell1);

      var cell2 = new mxCell(
        '',
        new mxGeometry(0.5, 1, 14, 14),
        'html=1;whiteSpace=wrap;shape=parallelMarker;outlineConnect=0;'
      );
      cell2.vertex = true;
      cell2.geometry.relative = true;
      cell2.geometry.offset = new mxPoint(-7, -14);
      cell.insert(cell2);

      return sb.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Data Object');
    }),
    this.createVertexTemplateEntry(
      'shape=datastore;whiteSpace=wrap;html=1;',
      60,
      60,
      '',
      'Data Store',
      null,
      null,
      'bpmn data store'
    ),
    this.createVertexTemplateEntry(
      'shape=plus;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'Sub-Process Marker',
      null,
      null,
      'bpmn subprocess sub process sub-process marker'
    ),
    this.createVertexTemplateEntry(
      'shape=mxgraph.bpmn.loop;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'Loop Marker',
      null,
      null,
      'bpmn loop marker'
    ),
    this.createVertexTemplateEntry(
      'shape=parallelMarker;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'Parallel MI Marker',
      null,
      null,
      'bpmn parallel mi marker'
    ),
    this.createVertexTemplateEntry(
      'shape=parallelMarker;direction=south;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'Sequential MI Marker',
      null,
      null,
      'bpmn sequential mi marker'
    ),
    this.createVertexTemplateEntry(
      'shape=mxgraph.bpmn.ad_hoc;fillColor=#000000;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'Ad Hoc Marker',
      null,
      null,
      'bpmn ad hoc marker'
    ),
    this.createVertexTemplateEntry(
      'shape=mxgraph.bpmn.compensation;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'Compensation Marker',
      null,
      null,
      'bpmn compensation marker'
    ),
    this.createVertexTemplateEntry(
      'shape=message;whiteSpace=wrap;html=1;outlineConnect=0;fillColor=#000000;strokeColor=#ffffff;strokeWidth=2;',
      40,
      30,
      '',
      'Send Task',
      null,
      null,
      'bpmn send task'
    ),
    this.createVertexTemplateEntry(
      'shape=message;whiteSpace=wrap;html=1;outlineConnect=0;',
      40,
      30,
      '',
      'Receive Task',
      null,
      null,
      'bpmn receive task'
    ),
    this.createVertexTemplateEntry(
      'shape=mxgraph.bpmn.user_task;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'User Task',
      null,
      null,
      this.getTagsForStencil('mxgraph.bpmn', 'user_task').join(' ')
    ),
    this.createVertexTemplateEntry(
      'shape=mxgraph.bpmn.manual_task;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'Manual Task',
      null,
      null,
      this.getTagsForStencil('mxgraph.bpmn', 'user_task').join(' ')
    ),
    this.createVertexTemplateEntry(
      'shape=mxgraph.bpmn.business_rule_task;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'Business Rule Task',
      null,
      null,
      this.getTagsForStencil('mxgraph.bpmn', 'business_rule_task').join(' ')
    ),
    this.createVertexTemplateEntry(
      'shape=mxgraph.bpmn.service_task;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'Service Task',
      null,
      null,
      this.getTagsForStencil('mxgraph.bpmn', 'service_task').join(' ')
    ),
    this.createVertexTemplateEntry(
      'shape=mxgraph.bpmn.script_task;html=1;outlineConnect=0;',
      14,
      14,
      '',
      'Script Task',
      null,
      null,
      this.getTagsForStencil('mxgraph.bpmn', 'script_task').join(' ')
    ),
    this.createVertexTemplateEntry(
      'html=1;shape=mxgraph.flowchart.annotation_2;align=left;labelPosition=right;',
      50,
      100,
      '',
      'Annotation',
      null,
      null,
      this.getTagsForStencil('bpmn', 'annotation_1', 'bpmn business process model ').join(' ')
    ),
    this.addDataEntry(
      'crossfunctional cross-functional cross functional flowchart swimlane table',
      400,
      400,
      'Cross-Functional Flowchart',
      '7ZhRb5swEMc/DY+bMCRt97jQpi+tVC2fwINbbMnYyD4C6aefjaHpBrTRlNCoTALJPp9t+P25O5kgTvL6XtOCPaoMRBDfBXGilULfyusEhAiikGdBfBtEUWjvIFqPjJJmNCyoBonHTIj8hB0VJXiL3dyYL+tSpsiVpM55LVSVMqrROxvci9bZMFq4JtKfzrRKGRfZA92rEjtr11tpVT1wCcYOhM5ViTKXry0G7RYb/uwWXDgDw9wCuSW2WTGOsClo6gYri8uvIGhheLN1s4KGtNSG7+AHGL+Os0JdUJm1nUJxiaDvdhZQt/EvJXHTvpTbjAq+lbadgnO1hhYSaIR6FHRjainfg8oB9d66VDxD5j0WoRcjZMC3DP8yUuMN25e5B91so5VuWMa4J+P3FJW2JtLXrOK5oNLJxZTmz/blqXhNp3mO5cpe9smS8OsyWNp5ie2TQ99ezl1joqRBTXmDAajBCgxejprHKBcNK7fvBPIz3hOSRCcQctET8olRA+8JmSopIW2j8GOD6Sji8TDxepT4C9yTE1+OEo/mQ5xcTYn8ahR5PB/k0c2UyK9HC8SbX/mnLBAnqAlD8XK+onDTE+/fw+TiQF9fTin4Nl/O0xYAEs6X9LR5n5Ae6S7xv1lr/yf+4cQ/pN75Ej/pH88/UZyQkRPzR6R+0j9Bz4f0xMm/f8adD+qzZn/bPfw5bMb++LH4Gw=='
    ),
    this.addDataEntry(
      'container swimlane pool horizontal',
      480,
      380,
      'Horizontal Pool 1',
      'zZRLbsIwEIZP4709TlHXhJYNSEicwCIjbNWJkWNKwumZxA6IlrRUaisWlmb+eX8LM5mXzdyrnV66Ai2TL0zm3rkQrbLJ0VoG3BRMzhgAp8fgdSQq+ijfKY9VuKcAYsG7snuMyso5G8U6tDaJ9cGUVlXkTXUoacuZIHOjjS0WqnX7blYd1OZt8KYea3PE1bCI+CAtVUMq7/o5b46uCmroSn18WFMm+XCdse5GpLq0OPqAzejxvZQun6MrMfiWUg6mCDpmZM8RENdotjqVyUFUdRS259oLSzISztto5Se0i44gcHEn3i9A/IQB3GbQpmi69DskAn4BSTaGBB4Jicj+k8nTGBP5SExg8odMyL38eH3s6kM8AQ=='
    ),
    this.addDataEntry(
      'container swimlane pool horizontal',
      480,
      360,
      'Horizontal Pool 2',
      'zZTBbsIwDIafJvfU6dDOlI0LSEg8QUQtEi1tUBJGy9PPbcJQWTsxaZs4VLJ//07sT1WYKKpm6eRBrW2JhokXJgpnbYhR1RRoDAOuSyYWDIDTx+B1opr1VX6QDutwTwPEhndpjhiVjbUmij60Jon+pCsja8rmKlQ05SKjcKe0KVeytcfuLh/k7u2SzR16fcbNZZDsRlrLhlTenWedPts6SJMEOseFLTkph6Fj212RbGlwdAGbyeV7KW2+RFthcC1ZTroMKjry5wiIK9R7ldrELInSR2H/2XtlSUHCOY5WfEG76ggCz+7E+w2InzCAcQapIf0fAySzESQZ/AKSfAoJPCKS9mbzf0H0NIVIPDAiyP8QEaXX97CvDZ7LDw=='
    ),
    this.createVertexTemplateEntry(
      'swimlane;startSize=20;horizontal=0;',
      320,
      120,
      'Lane',
      'Horizontal Swimlane',
      null,
      null,
      'swimlane lane pool'
    ),
    this.addDataEntry(
      'container swimlane pool horizontal',
      360,
      480,
      'Vertical Pool 1',
      'xZRBbsIwEEVP4709ThFrQssGJKSewCIjbNXGyDEl4fSdxKa0NJFQVTULSzP/e+T5b2EmS9esgjrqja/QMvnMZBm8j6lyTYnWMuCmYnLJADgdBi8jruhdflQBD/GRAUgD78qeMClb720S69jaLNZn46w6ULfQ0dGWS0HlThtbrVXrT91bdVS7t2u3CFibC26vi4g7aaMaUjmpNBbiKxnUQyfkjTBEbEZT9VKOtELvMIaWrpxNFXW6IWcpOddo9jqPFfMsqjoJ+8/ZGyQqMqdhZvIHs3WHBrh4kNvvIsNw5Da7OdgXAgKGCMz+gEAxRgCmINDcxZ2CyNMYETkhESj+jwi1t1+r9759ah8='
    ),
    this.addDataEntry(
      'container swimlane pool vertical',
      380,
      480,
      'Vertical Pool 2',
      'xZTPbsIwDMafJvf86dDOlI0LSEg8QUQtEi1pUBJGy9PPbdJ1G1TqhXGoZH/219g/RSGitM3ay5PaugoMEW9ElN65mCLblGAM4VRXRKwI5xQ/wt8nqqyv0pP0UMc5Bp4Mn9KcISk750wSQ2xNFsNFWyNrzJYqWpxyxTA8KG2qjWzduTsrRHn4GLKlh6CvsBsGYX+krWxQpaiizcc9FjDnnaCc11dXR2lyxyjsuyPy3/Lg4CM0k8v3Ut58Dc5C9C22XHQVVeoQrwkQVaCPKtuKQZQhCcdv78gSg4zzPlpxg3bTEeSUzcR7Q2bWyvz+ytmQr8NPAow/ikAxRYA/kQAr/hPByxQC8cxLsHggAkzH56uv/XrdvgA='
    ),
    this.createVertexTemplateEntry(
      'swimlane;startSize=20;',
      120,
      320,
      'Lane',
      'Vertical Swimlane',
      null,
      null,
      'swimlane lane pool'
    ),
    this.createVertexTemplateEntry(
      'rounded=1;arcSize=10;dashed=1;strokeColor=#000000;fillColor=none;gradientColor=none;dashPattern=8 3 1 3;strokeWidth=2;',
      200,
      200,
      '',
      'Group',
      null,
      null,
      this.getTagsForStencil('bpmn', 'group', 'bpmn business process model ').join(' ')
    ),
    this.createEdgeTemplateEntry(
      'endArrow=block;endFill=1;endSize=6;html=1;',
      100,
      0,
      '',
      'Sequence Flow',
      null,
      'bpmn sequence flow'
    ),
    this.createEdgeTemplateEntry(
      'startArrow=dash;startSize=8;endArrow=block;endFill=1;endSize=6;html=1;',
      100,
      0,
      '',
      'Default Flow',
      null,
      'bpmn default flow'
    ),
    this.createEdgeTemplateEntry(
      'startArrow=diamondThin;startFill=0;startSize=14;endArrow=block;endFill=1;endSize=6;html=1;',
      100,
      0,
      '',
      'Conditional Flow',
      null,
      'bpmn conditional flow'
    ),
    this.createEdgeTemplateEntry(
      'startArrow=oval;startFill=0;startSize=7;endArrow=block;endFill=0;endSize=10;dashed=1;html=1;',
      100,
      0,
      '',
      'Message Flow 1',
      null,
      'bpmn message flow'
    ),
    this.addEntry('bpmn message flow', function () {
      var edge = new mxCell(
        '',
        new mxGeometry(0, 0, 0, 0),
        'startArrow=oval;startFill=0;startSize=7;endArrow=block;endFill=0;endSize=10;dashed=1;html=1;'
      );
      edge.geometry.setTerminalPoint(new mxPoint(0, 0), true);
      edge.geometry.setTerminalPoint(new mxPoint(100, 0), false);
      edge.geometry.relative = true;
      edge.edge = true;

      var cell = new mxCell('', new mxGeometry(0, 0, 20, 14), 'shape=message;html=1;outlineConnect=0;');
      cell.geometry.relative = true;
      cell.vertex = true;
      cell.geometry.offset = new mxPoint(-10, -7);
      edge.insert(cell);

      return sb.createEdgeTemplateFromCells([edge], 100, 0, 'Message Flow 2');
    }),
    this.createEdgeTemplateEntry('shape=link;html=1;', 100, 0, '', 'Link', null, 'bpmn link'),
  ];

  this.addPaletteFunctions('bpmn', 'BPMN ' + mxResources.get('general'), false, fns);
  this.setCurrentSearchEntryLibrary();
};

/**
 * Creates and returns the given title element.
 */
Sidebar.prototype.createTitle = function (label) {
  var elt = document.createElement('a');
  elt.setAttribute('title', mxResources.get('sidebarTooltip'));
  elt.className = 'geTitle';
  mxUtils.write(elt, label);

  return elt;
};

/**
 * Creates a thumbnail for the given cells.
 */
Sidebar.prototype.createThumb = function (
  cells,
  width,
  height,
  parent,
  title,
  showLabel,
  showTitle,
  realWidth,
  realHeight
) {
  this.graph.labelsVisible = showLabel == null || showLabel;
  var fo = mxClient.NO_FO;
  mxClient.NO_FO = Editor.prototype.originalNoForeignObject;
  this.graph.view.scaleAndTranslate(1, 0, 0);
  this.graph.addCells(cells);
  var bounds = this.graph.getGraphBounds();
  var s =
    Math.floor(
      Math.min((width - 2 * this.thumbBorder) / bounds.width, (height - 2 * this.thumbBorder) / bounds.height) * 100
    ) / 100;
  this.graph.view.scaleAndTranslate(
    s,
    Math.floor((width - bounds.width * s) / 2 / s - bounds.x),
    Math.floor((height - bounds.height * s) / 2 / s - bounds.y)
  );
  var node = null;

  // For supporting HTML labels in IE9 standards mode the container is cloned instead
  if (
    this.graph.dialect == mxConstants.DIALECT_SVG &&
    !mxClient.NO_FO &&
    this.graph.view.getCanvas().ownerSVGElement != null
  ) {
    node = this.graph.view.getCanvas().ownerSVGElement.cloneNode(true);
  }
  // LATER: Check if deep clone can be used for quirks if container in DOM
  else {
    node = this.graph.container.cloneNode(false);
    node.innerHTML = this.graph.container.innerHTML;

    // Workaround for clipping in older IE versions
    if (mxClient.IS_QUIRKS || document.documentMode == 8) {
      node.firstChild.style.overflow = 'visible';
    }
  }

  this.graph.getModel().clear();
  mxClient.NO_FO = fo;

  // Catch-all event handling
  if (mxClient.IS_IE6) {
    parent.style.backgroundImage = 'url(' + this.editorUi.editor.transparentImage + ')';
  }

  node.style.position = 'relative';
  node.style.overflow = 'hidden';
  node.style.left = this.thumbBorder + 'px';
  node.style.top = this.thumbBorder + 'px';
  node.style.width = width + 'px';
  node.style.height = height + 'px';
  node.style.visibility = '';
  node.style.minWidth = '';
  node.style.minHeight = '';

  parent.appendChild(node);

  // Adds title for sidebar entries
  if (this.sidebarTitles && title != null && showTitle != false) {
    var border = mxClient.IS_QUIRKS ? 2 * this.thumbPadding + 2 : 0;
    parent.style.height = this.thumbHeight + border + this.sidebarTitleSize + 8 + 'px';

    var div = document.createElement('div');
    div.style.fontSize = this.sidebarTitleSize + 'px';
    div.style.color = '#303030';
    div.style.textAlign = 'center';
    div.style.whiteSpace = 'nowrap';

    if (mxClient.IS_IE) {
      div.style.height = this.sidebarTitleSize + 12 + 'px';
    }

    div.style.paddingTop = '4px';
    mxUtils.write(div, title);
    parent.appendChild(div);
  }

  return bounds;
};

/**
 * Creates and returns a new palette item for the given image.
 */
Sidebar.prototype.createItem = function (cells, title, showLabel, showTitle, width, height, allowCellsInserted) {
  var elt = document.createElement('a');
  elt.className = 'geItem';
  elt.style.overflow = 'hidden';
  var border = mxClient.IS_QUIRKS ? 8 + 2 * this.thumbPadding : 2 * this.thumbBorder;
  elt.style.width = this.thumbWidth + border + 'px';
  elt.style.height = this.thumbHeight + border + 'px';
  elt.style.padding = this.thumbPadding + 'px';

  if (mxClient.IS_IE6) {
    elt.style.border = 'none';
  }

  // Blocks default click action
  mxEvent.addListener(elt, 'click', function (evt) {
    mxEvent.consume(evt);
  });

  this.createThumb(cells, this.thumbWidth, this.thumbHeight, elt, title, showLabel, showTitle, width, height);
  var bounds = new mxRectangle(0, 0, width, height);

  if (cells.length > 1 || cells[0].vertex) {
    var ds = this.createDragSource(
      elt,
      this.createDropHandler(cells, true, allowCellsInserted, bounds),
      this.createDragPreview(width, height),
      cells,
      bounds
    );
    this.addClickHandler(elt, ds, cells);

    // Uses guides for vertices only if enabled in graph
    ds.isGuidesEnabled = mxUtils.bind(this, function () {
      return this.editorUi.editor.graph.graphHandler.guidesEnabled;
    });
  } else if (cells[0] != null && cells[0].edge) {
    var ds = this.createDragSource(
      elt,
      this.createDropHandler(cells, false, allowCellsInserted, bounds),
      this.createDragPreview(width, height),
      cells,
      bounds
    );
    this.addClickHandler(elt, ds, cells);
  }

  // Shows a tooltip with the rendered cell
  if (!mxClient.IS_IOS) {
    mxEvent.addGestureListeners(
      elt,
      null,
      mxUtils.bind(this, function (evt) {
        if (mxEvent.isMouseEvent(evt)) {
          this.showTooltip(elt, cells, bounds.width, bounds.height, title, showLabel);
        }
      })
    );
  }

  return elt;
};

/**
 * Creates a drop handler for inserting the given cells.
 */
Sidebar.prototype.updateShapes = function (source, targets) {
  var graph = this.editorUi.editor.graph;
  var sourceCellStyle = graph.getCellStyle(source);
  var result = [];

  graph.model.beginUpdate();
  try {
    var cellStyle = graph.getModel().getStyle(source);

    // Lists the styles to carry over from the existing shape
    var styles = [
      'shadow',
      'dashed',
      'dashPattern',
      'fontFamily',
      'fontSize',
      'fontColor',
      'align',
      'startFill',
      'startSize',
      'endFill',
      'endSize',
      'strokeColor',
      'strokeWidth',
      'fillColor',
      'gradientColor',
      'html',
      'part',
      'noEdgeStyle',
      'edgeStyle',
      'elbow',
      'childLayout',
      'recursiveResize',
      'container',
      'collapsible',
      'connectable',
      'comic',
      'sketch',
      'fillWeight',
      'hachureGap',
      'hachureAngle',
      'jiggle',
      'disableMultiStroke',
      'disableMultiStrokeFill',
      'fillStyle',
      'curveFitting',
      'simplification',
      'sketchStyle',
    ];

    for (var i = 0; i < targets.length; i++) {
      var targetCell = targets[i];

      if (
        graph.getModel().isVertex(targetCell) == graph.getModel().isVertex(source) ||
        graph.getModel().isEdge(targetCell) == graph.getModel().isEdge(source)
      ) {
        var style = graph.getCurrentCellStyle(targets[i]);
        graph.getModel().setStyle(targetCell, cellStyle);

        // Removes all children of composite cells
        if (mxUtils.getValue(style, 'composite', '0') == '1') {
          var childCount = graph.model.getChildCount(targetCell);

          for (var j = childCount; j >= 0; j--) {
            graph.model.remove(graph.model.getChildAt(targetCell, j));
          }
        }

        // Replaces the participant style in the lifeline shape with the target shape
        if (
          style[mxConstants.STYLE_SHAPE] == 'umlLifeline' &&
          sourceCellStyle[mxConstants.STYLE_SHAPE] != 'umlLifeline'
        ) {
          graph.setCellStyles(mxConstants.STYLE_SHAPE, 'umlLifeline', [targetCell]);
          graph.setCellStyles('participant', sourceCellStyle[mxConstants.STYLE_SHAPE], [targetCell]);
        }

        for (var j = 0; j < styles.length; j++) {
          var value = style[styles[j]];

          if (value != null) {
            graph.setCellStyles(styles[j], value, [targetCell]);
          }
        }

        result.push(targetCell);
      }
    }
  } finally {
    graph.model.endUpdate();
  }

  return result;
};

/**
 * Creates a drop handler for inserting the given cells.
 */
Sidebar.prototype.createDropHandler = function (cells, allowSplit, allowCellsInserted, bounds) {
  allowCellsInserted = allowCellsInserted != null ? allowCellsInserted : true;

  return mxUtils.bind(this, function (graph, evt, target, x, y, force) {
    var elt = force
      ? null
      : mxEvent.isTouchEvent(evt) || mxEvent.isPenEvent(evt)
      ? document.elementFromPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt))
      : mxEvent.getSource(evt);

    while (elt != null && elt != this.container) {
      elt = elt.parentNode;
    }

    if (elt == null && graph.isEnabled()) {
      cells = graph.getImportableCells(cells);

      if (cells.length > 0) {
        graph.stopEditing();

        // Holding alt while mouse is released ignores drop target
        var validDropTarget =
          target != null && !mxEvent.isAltDown(evt) ? graph.isValidDropTarget(target, cells, evt) : false;

        var select = null;

        if (target != null && !validDropTarget) {
          target = null;
        }

        if (!graph.isCellLocked(target || graph.getDefaultParent())) {
          graph.model.beginUpdate();
          try {
            x = Math.round(x);
            y = Math.round(y);

            // Splits the target edge or inserts into target group
            if (allowSplit && graph.isSplitTarget(target, cells, evt)) {
              var s = graph.view.scale;
              var tr = graph.view.translate;
              var tx = (x + tr.x) * s;
              var ty = (y + tr.y) * s;

              var clones = graph.cloneCells(cells);
              graph.splitEdge(target, clones, null, x - bounds.width / 2, y - bounds.height / 2, tx, ty);
              select = clones;
            } else if (cells.length > 0) {
              select = graph.importCells(cells, x, y, target);
            }

            // Executes parent layout hooks for position/order
            if (graph.layoutManager != null) {
              var layout = graph.layoutManager.getLayout(target);

              if (layout != null) {
                var s = graph.view.scale;
                var tr = graph.view.translate;
                var tx = (x + tr.x) * s;
                var ty = (y + tr.y) * s;

                for (var i = 0; i < select.length; i++) {
                  layout.moveCell(select[i], tx, ty);
                }
              }
            }

            if (allowCellsInserted && (evt == null || !mxEvent.isShiftDown(evt))) {
              graph.fireEvent(new mxEventObject('cellsInserted', 'cells', select));
            }
          } catch (e) {
            this.editorUi.handleError(e);
          } finally {
            graph.model.endUpdate();
          }

          if (select != null && select.length > 0) {
            graph.scrollCellToVisible(select[0]);
            graph.setSelectionCells(select);
          }

          if (
            graph.editAfterInsert &&
            evt != null &&
            mxEvent.isMouseEvent(evt) &&
            select != null &&
            select.length == 1
          ) {
            window.setTimeout(function () {
              graph.startEditing(select[0]);
            }, 0);
          }
        }
      }

      mxEvent.consume(evt);
    }
  });
};

/**
 * Creates and returns a preview element for the given width and height.
 */
Sidebar.prototype.createDragPreview = function (width, height) {
  var elt = document.createElement('div');
  elt.style.border = this.dragPreviewBorder;
  elt.style.width = width + 'px';
  elt.style.height = height + 'px';

  return elt;
};

/**
 * Creates a drag source for the given element.
 */
Sidebar.prototype.dropAndConnect = function (source, targets, direction, dropCellIndex, evt) {
  var geo = this.getDropAndConnectGeometry(source, targets[dropCellIndex], direction, targets);

  // Targets without the new edge for selection
  var tmp = [];

  if (geo != null) {
    var graph = this.editorUi.editor.graph;
    var editingCell = null;

    graph.model.beginUpdate();
    try {
      var sourceGeo = graph.getCellGeometry(source);
      var geo2 = graph.getCellGeometry(targets[dropCellIndex]);

      // Handles special case where target should be ignored for stack layouts
      var targetParent = graph.model.getParent(source);
      var validLayout = true;

      // Ignores parent if it has a stack layout or if it is a table or row
      if (graph.layoutManager != null) {
        var layout = graph.layoutManager.getLayout(targetParent);

        // LATER: Use parent of parent if valid layout
        if (layout != null && layout.constructor == mxStackLayout) {
          validLayout = false;
        }
      }

      // Checks if another container is at the drop location
      var tmp = graph.model.isEdge(source) ? null : graph.view.getState(targetParent);
      var dx = 0;
      var dy = 0;

      // Offsets by parent position
      if (tmp != null) {
        var offset = tmp.origin;
        dx = offset.x;
        dy = offset.y;

        var pt = geo.getTerminalPoint(false);

        if (pt != null) {
          pt.x += offset.x;
          pt.y += offset.y;
        }
      }

      var useParent =
        !graph.isTableRow(source) &&
        !graph.isTableCell(source) &&
        (graph.model.isEdge(source) || (sourceGeo != null && !sourceGeo.relative && validLayout));

      var tempTarget = graph.getCellAt(
        (geo.x + dx + graph.view.translate.x) * graph.view.scale,
        (geo.y + dy + graph.view.translate.y) * graph.view.scale,
        null,
        null,
        null,
        function (state, x, y) {
          return !graph.isContainer(state.cell);
        }
      );

      if (tempTarget != null && tempTarget != targetParent) {
        tmp = graph.view.getState(tempTarget);

        // Offsets by new parent position
        if (tmp != null) {
          var offset = tmp.origin;
          targetParent = tempTarget;
          useParent = true;

          if (!graph.model.isEdge(source)) {
            geo.x -= offset.x - dx;
            geo.y -= offset.y - dy;
          }
        }
      } else if (!validLayout || graph.isTableRow(source) || graph.isTableCell(source)) {
        geo.x += dx;
        geo.y += dy;
      }

      dx = geo2.x;
      dy = geo2.y;

      // Ignores geometry of edges
      if (graph.model.isEdge(targets[dropCellIndex])) {
        dx = 0;
        dy = 0;
      }

      targets = graph.importCells(
        targets,
        geo.x - (useParent ? dx : 0),
        geo.y - (useParent ? dy : 0),
        useParent ? targetParent : null
      );
      tmp = targets;

      if (graph.model.isEdge(source)) {
        // Adds new terminal to edge
        // LATER: Push new terminal out radially from edge start point
        graph.model.setTerminal(source, targets[dropCellIndex], direction == mxConstants.DIRECTION_NORTH);
      } else if (graph.model.isEdge(targets[dropCellIndex])) {
        // Adds new outgoing connection to vertex and clears points
        graph.model.setTerminal(targets[dropCellIndex], source, true);
        var geo3 = graph.getCellGeometry(targets[dropCellIndex]);
        geo3.points = null;

        if (geo3.getTerminalPoint(false) != null) {
          geo3.setTerminalPoint(geo.getTerminalPoint(false), false);
        } else if (useParent && graph.model.isVertex(targetParent)) {
          // Adds parent offset to other nodes
          var tmpState = graph.view.getState(targetParent);
          var offset = tmpState.cell != graph.view.currentRoot ? tmpState.origin : new mxPoint(0, 0);

          graph.cellsMoved(targets, offset.x, offset.y, null, null, true);
        }
      } else {
        geo2 = graph.getCellGeometry(targets[dropCellIndex]);
        dx = geo.x - Math.round(geo2.x);
        dy = geo.y - Math.round(geo2.y);
        geo.x = Math.round(geo2.x);
        geo.y = Math.round(geo2.y);
        graph.model.setGeometry(targets[dropCellIndex], geo);
        graph.cellsMoved(targets, dx, dy, null, null, true);
        tmp = targets.slice();
        editingCell = tmp.length == 1 ? tmp[0] : null;
        targets.push(graph.insertEdge(null, null, '', source, targets[dropCellIndex], graph.createCurrentEdgeStyle()));
      }

      if (evt == null || !mxEvent.isShiftDown(evt)) {
        graph.fireEvent(new mxEventObject('cellsInserted', 'cells', targets));
      }
    } catch (e) {
      this.editorUi.handleError(e);
    } finally {
      graph.model.endUpdate();
    }

    if (graph.editAfterInsert && evt != null && mxEvent.isMouseEvent(evt) && editingCell != null) {
      window.setTimeout(function () {
        graph.startEditing(editingCell);
      }, 0);
    }
  }

  return tmp;
};

/**
 * Creates a drag source for the given element.
 */
Sidebar.prototype.getDropAndConnectGeometry = function (source, target, direction, targets) {
  var graph = this.editorUi.editor.graph;
  var view = graph.view;
  var keepSize = targets.length > 1;
  var geo = graph.getCellGeometry(source);
  var geo2 = graph.getCellGeometry(target);

  if (geo != null && geo2 != null) {
    geo2 = geo2.clone();

    if (graph.model.isEdge(source)) {
      var state = graph.view.getState(source);
      var pts = state.absolutePoints;
      var p0 = pts[0];
      var pe = pts[pts.length - 1];

      if (direction == mxConstants.DIRECTION_NORTH) {
        geo2.x = p0.x / view.scale - view.translate.x - geo2.width / 2;
        geo2.y = p0.y / view.scale - view.translate.y - geo2.height / 2;
      } else {
        geo2.x = pe.x / view.scale - view.translate.x - geo2.width / 2;
        geo2.y = pe.y / view.scale - view.translate.y - geo2.height / 2;
      }
    } else {
      if (geo.relative) {
        var state = graph.view.getState(source);
        geo = geo.clone();
        geo.x = (state.x - view.translate.x) / view.scale;
        geo.y = (state.y - view.translate.y) / view.scale;
      }

      var length = graph.defaultEdgeLength;

      // Maintains edge length
      if (graph.model.isEdge(target) && geo2.getTerminalPoint(true) != null && geo2.getTerminalPoint(false) != null) {
        var p0 = geo2.getTerminalPoint(true);
        var pe = geo2.getTerminalPoint(false);
        var dx = pe.x - p0.x;
        var dy = pe.y - p0.y;

        length = Math.sqrt(dx * dx + dy * dy);

        geo2.x = geo.getCenterX();
        geo2.y = geo.getCenterY();
        geo2.width = 1;
        geo2.height = 1;

        if (direction == mxConstants.DIRECTION_NORTH) {
          geo2.height = length;
          geo2.y = geo.y - length;
          geo2.setTerminalPoint(new mxPoint(geo2.x, geo2.y), false);
        } else if (direction == mxConstants.DIRECTION_EAST) {
          geo2.width = length;
          geo2.x = geo.x + geo.width;
          geo2.setTerminalPoint(new mxPoint(geo2.x + geo2.width, geo2.y), false);
        } else if (direction == mxConstants.DIRECTION_SOUTH) {
          geo2.height = length;
          geo2.y = geo.y + geo.height;
          geo2.setTerminalPoint(new mxPoint(geo2.x, geo2.y + geo2.height), false);
        } else if (direction == mxConstants.DIRECTION_WEST) {
          geo2.width = length;
          geo2.x = geo.x - length;
          geo2.setTerminalPoint(new mxPoint(geo2.x, geo2.y), false);
        }
      } else {
        // Try match size or ignore if width or height < 45 which
        // is considered special enough to be ignored here
        if (!keepSize && geo2.width > 45 && geo2.height > 45 && geo.width > 45 && geo.height > 45) {
          geo2.width = geo2.width * (geo.height / geo2.height);
          geo2.height = geo.height;
        }

        geo2.x = geo.x + geo.width / 2 - geo2.width / 2;
        geo2.y = geo.y + geo.height / 2 - geo2.height / 2;

        if (direction == mxConstants.DIRECTION_NORTH) {
          geo2.y = geo2.y - geo.height / 2 - geo2.height / 2 - length;
        } else if (direction == mxConstants.DIRECTION_EAST) {
          geo2.x = geo2.x + geo.width / 2 + geo2.width / 2 + length;
        } else if (direction == mxConstants.DIRECTION_SOUTH) {
          geo2.y = geo2.y + geo.height / 2 + geo2.height / 2 + length;
        } else if (direction == mxConstants.DIRECTION_WEST) {
          geo2.x = geo2.x - geo.width / 2 - geo2.width / 2 - length;
        }

        // Adds offset to match cells without connecting edge
        if (graph.model.isEdge(target) && geo2.getTerminalPoint(true) != null && target.getTerminal(false) != null) {
          var targetGeo = graph.getCellGeometry(target.getTerminal(false));

          if (targetGeo != null) {
            if (direction == mxConstants.DIRECTION_NORTH) {
              geo2.x -= targetGeo.getCenterX();
              geo2.y -= targetGeo.getCenterY() + targetGeo.height / 2;
            } else if (direction == mxConstants.DIRECTION_EAST) {
              geo2.x -= targetGeo.getCenterX() - targetGeo.width / 2;
              geo2.y -= targetGeo.getCenterY();
            } else if (direction == mxConstants.DIRECTION_SOUTH) {
              geo2.x -= targetGeo.getCenterX();
              geo2.y -= targetGeo.getCenterY() - targetGeo.height / 2;
            } else if (direction == mxConstants.DIRECTION_WEST) {
              geo2.x -= targetGeo.getCenterX() + targetGeo.width / 2;
              geo2.y -= targetGeo.getCenterY();
            }
          }
        }
      }
    }
  }

  return geo2;
};

/**
 * Limits drop style to non-transparent source shapes.
 */
Sidebar.prototype.isDropStyleEnabled = function (cells, firstVertex) {
  var result = true;

  if (firstVertex != null && cells.length == 1) {
    var vstyle = this.graph.getCellStyle(cells[firstVertex]);

    if (vstyle != null) {
      result =
        mxUtils.getValue(vstyle, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE) != mxConstants.NONE ||
        mxUtils.getValue(vstyle, mxConstants.STYLE_FILLCOLOR, mxConstants.NONE) != mxConstants.NONE;
    }
  }

  return result;
};

/**
 * Ignores swimlanes as drop style targets.
 */
Sidebar.prototype.isDropStyleTargetIgnored = function (state) {
  return (
    this.graph.isSwimlane(state.cell) ||
    this.graph.isTableCell(state.cell) ||
    this.graph.isTableRow(state.cell) ||
    this.graph.isTable(state.cell)
  );
};

/**
 * Creates a drag source for the given element.
 */
Sidebar.prototype.createDragSource = function (elt, dropHandler, preview, cells, bounds) {
  // Checks if the cells contain any vertices
  var ui = this.editorUi;
  var graph = ui.editor.graph;
  var freeSourceEdge = null;
  var firstVertex = null;
  var sidebar = this;

  for (var i = 0; i < cells.length; i++) {
    if (firstVertex == null && graph.model.isVertex(cells[i])) {
      firstVertex = i;
    } else if (
      freeSourceEdge == null &&
      graph.model.isEdge(cells[i]) &&
      graph.model.getTerminal(cells[i], true) == null
    ) {
      freeSourceEdge = i;
    }

    if (firstVertex != null && freeSourceEdge != null) {
      break;
    }
  }

  var dropStyleEnabled = this.isDropStyleEnabled(cells, firstVertex);

  var dragSource = mxUtils.makeDraggable(
    elt,
    graph,
    mxUtils.bind(this, function (graph, evt, target, x, y) {
      if (this.updateThread != null) {
        window.clearTimeout(this.updateThread);
      }

      if (cells != null && currentStyleTarget != null && activeArrow == styleTarget) {
        var tmp = graph.isCellSelected(currentStyleTarget.cell) ? graph.getSelectionCells() : [currentStyleTarget.cell];
        var updatedCells = this.updateShapes(
          graph.model.isEdge(currentStyleTarget.cell) ? cells[0] : cells[firstVertex],
          tmp
        );
        graph.setSelectionCells(updatedCells);
      } else if (cells != null && activeArrow != null && currentTargetState != null && activeArrow != styleTarget) {
        var index =
          graph.model.isEdge(currentTargetState.cell) || freeSourceEdge == null ? firstVertex : freeSourceEdge;
        graph.setSelectionCells(this.dropAndConnect(currentTargetState.cell, cells, direction, index, evt));
      } else {
        dropHandler.apply(this, arguments);
      }

      if (this.editorUi.hoverIcons != null) {
        this.editorUi.hoverIcons.update(graph.view.getState(graph.getSelectionCell()));
      }
    }),
    preview,
    0,
    0,
    graph.autoscroll,
    true,
    true
  );

  // Stops dragging if cancel is pressed
  graph.addListener(mxEvent.ESCAPE, function (sender, evt) {
    if (dragSource.isActive()) {
      dragSource.reset();
    }
  });

  // Overrides mouseDown to ignore popup triggers
  var mouseDown = dragSource.mouseDown;

  dragSource.mouseDown = function (evt) {
    if (!mxEvent.isPopupTrigger(evt) && !mxEvent.isMultiTouchEvent(evt)) {
      graph.stopEditing();
      mouseDown.apply(this, arguments);
    }
  };

  // Workaround for event redirection via image tag in quirks and IE8
  function createArrow(img, tooltip) {
    var arrow = null;

    if (mxClient.IS_IE && !mxClient.IS_SVG) {
      // Workaround for PNG images in IE6
      if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat') {
        arrow = document.createElement(mxClient.VML_PREFIX + ':image');
        arrow.setAttribute('src', img.src);
        arrow.style.borderStyle = 'none';
      } else {
        arrow = document.createElement('div');
        arrow.style.backgroundImage = 'url(' + img.src + ')';
        arrow.style.backgroundPosition = 'center';
        arrow.style.backgroundRepeat = 'no-repeat';
      }

      arrow.style.width = img.width + 4 + 'px';
      arrow.style.height = img.height + 4 + 'px';
      arrow.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block';
    } else {
      arrow = mxUtils.createImage(img.src);
      arrow.style.width = img.width + 'px';
      arrow.style.height = img.height + 'px';
    }

    if (tooltip != null) {
      arrow.setAttribute('title', tooltip);
    }

    mxUtils.setOpacity(arrow, img == this.refreshTarget ? 30 : 20);
    arrow.style.position = 'absolute';
    arrow.style.cursor = 'crosshair';

    return arrow;
  }

  var currentTargetState = null;
  var currentStateHandle = null;
  var currentStyleTarget = null;
  var activeTarget = false;

  var arrowUp = createArrow(this.triangleUp, mxResources.get('connect'));
  var arrowRight = createArrow(this.triangleRight, mxResources.get('connect'));
  var arrowDown = createArrow(this.triangleDown, mxResources.get('connect'));
  var arrowLeft = createArrow(this.triangleLeft, mxResources.get('connect'));
  var styleTarget = createArrow(this.refreshTarget, mxResources.get('replace'));
  // Workaround for actual parentNode not being updated in old IE
  var styleTargetParent = null;
  var roundSource = createArrow(this.roundDrop);
  var roundTarget = createArrow(this.roundDrop);
  var direction = mxConstants.DIRECTION_NORTH;
  var activeArrow = null;

  function checkArrow(x, y, bounds, arrow) {
    if (arrow.parentNode != null) {
      if (mxUtils.contains(bounds, x, y)) {
        mxUtils.setOpacity(arrow, 100);
        activeArrow = arrow;
      } else {
        mxUtils.setOpacity(arrow, arrow == styleTarget ? 30 : 20);
      }
    }

    return bounds;
  }

  // Hides guides and preview if target is active
  var dsCreatePreviewElement = dragSource.createPreviewElement;

  // Stores initial size of preview element
  dragSource.createPreviewElement = function (graph) {
    var elt = dsCreatePreviewElement.apply(this, arguments);

    // Pass-through events required to tooltip on replace shape
    if (mxClient.IS_SVG) {
      elt.style.pointerEvents = 'none';
    }

    this.previewElementWidth = elt.style.width;
    this.previewElementHeight = elt.style.height;

    return elt;
  };

  // Shows/hides hover icons
  var dragEnter = dragSource.dragEnter;
  dragSource.dragEnter = function (graph, evt) {
    if (ui.hoverIcons != null) {
      ui.hoverIcons.setDisplay('none');
    }

    dragEnter.apply(this, arguments);
  };

  var dragExit = dragSource.dragExit;
  dragSource.dragExit = function (graph, evt) {
    if (ui.hoverIcons != null) {
      ui.hoverIcons.setDisplay('');
    }

    dragExit.apply(this, arguments);
  };

  dragSource.dragOver = function (graph, evt) {
    mxDragSource.prototype.dragOver.apply(this, arguments);

    if (this.currentGuide != null && activeArrow != null) {
      this.currentGuide.hide();
    }

    if (this.previewElement != null) {
      var view = graph.view;

      if (currentStyleTarget != null && activeArrow == styleTarget) {
        this.previewElement.style.display = graph.model.isEdge(currentStyleTarget.cell) ? 'none' : '';

        this.previewElement.style.left = currentStyleTarget.x + 'px';
        this.previewElement.style.top = currentStyleTarget.y + 'px';
        this.previewElement.style.width = currentStyleTarget.width + 'px';
        this.previewElement.style.height = currentStyleTarget.height + 'px';
      } else if (currentTargetState != null && activeArrow != null) {
        if (dragSource.currentHighlight != null && dragSource.currentHighlight.state != null) {
          dragSource.currentHighlight.hide();
        }

        var index =
          graph.model.isEdge(currentTargetState.cell) || freeSourceEdge == null ? firstVertex : freeSourceEdge;
        var geo = sidebar.getDropAndConnectGeometry(currentTargetState.cell, cells[index], direction, cells);
        var geo2 = !graph.model.isEdge(currentTargetState.cell) ? graph.getCellGeometry(currentTargetState.cell) : null;
        var geo3 = graph.getCellGeometry(cells[index]);
        var parent = graph.model.getParent(currentTargetState.cell);
        var dx = view.translate.x * view.scale;
        var dy = view.translate.y * view.scale;

        if (geo2 != null && !geo2.relative && graph.model.isVertex(parent) && parent != view.currentRoot) {
          var pState = view.getState(parent);

          dx = pState.x;
          dy = pState.y;
        }

        var dx2 = geo3.x;
        var dy2 = geo3.y;

        // Ignores geometry of edges
        if (graph.model.isEdge(cells[index])) {
          dx2 = 0;
          dy2 = 0;
        }

        // Shows preview at drop location
        this.previewElement.style.left = (geo.x - dx2) * view.scale + dx + 'px';
        this.previewElement.style.top = (geo.y - dy2) * view.scale + dy + 'px';

        if (cells.length == 1) {
          this.previewElement.style.width = geo.width * view.scale + 'px';
          this.previewElement.style.height = geo.height * view.scale + 'px';
        }

        this.previewElement.style.display = '';
      } else if (
        dragSource.currentHighlight.state != null &&
        graph.model.isEdge(dragSource.currentHighlight.state.cell)
      ) {
        // Centers drop cells when splitting edges
        this.previewElement.style.left =
          Math.round(parseInt(this.previewElement.style.left) - (bounds.width * view.scale) / 2) + 'px';
        this.previewElement.style.top =
          Math.round(parseInt(this.previewElement.style.top) - (bounds.height * view.scale) / 2) + 'px';
      } else {
        this.previewElement.style.width = this.previewElementWidth;
        this.previewElement.style.height = this.previewElementHeight;
        this.previewElement.style.display = '';
      }
    }
  };

  var startTime = new Date().getTime();
  var timeOnTarget = 0;
  var prev = null;

  // Gets source cell style to compare shape below
  var sourceCellStyle = this.editorUi.editor.graph.getCellStyle(cells[0]);

  // Allows drop into cell only if target is a valid root
  dragSource.getDropTarget = mxUtils.bind(this, function (graph, x, y, evt) {
    // Alt means no targets at all
    // LATER: Show preview where result will go
    var cell =
      !mxEvent.isAltDown(evt) && cells != null
        ? graph.getCellAt(x, y, null, null, null, function (state, x, y) {
            return graph.isContainer(state.cell);
          })
        : null;

    // Uses connectable parent vertex if one exists
    if (cell != null && !this.graph.isCellConnectable(cell) && !this.graph.model.isEdge(cell)) {
      var parent = this.graph.getModel().getParent(cell);

      if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) {
        cell = parent;
      }
    }

    // Ignores locked cells
    if (graph.isCellLocked(cell)) {
      cell = null;
    }

    var state = graph.view.getState(cell);
    activeArrow = null;
    var bbox = null;

    // Time on target
    if (prev != state) {
      startTime = new Date().getTime();
      timeOnTarget = 0;
      prev = state;

      if (this.updateThread != null) {
        window.clearTimeout(this.updateThread);
      }

      if (state != null) {
        this.updateThread = window.setTimeout(function () {
          if (activeArrow == null) {
            prev = state;
            dragSource.getDropTarget(graph, x, y, evt);
          }
        }, this.dropTargetDelay + 10);
      }
    } else {
      timeOnTarget = new Date().getTime() - startTime;
    }

    // Shift means disabled, delayed on cells with children, shows after this.dropTargetDelay, hides after 2500ms
    if (
      dropStyleEnabled &&
      timeOnTarget < 2500 &&
      state != null &&
      !mxEvent.isShiftDown(evt) &&
      // If shape is equal or target has no stroke, fill and gradient then use longer delay except for images
      ((mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE) !=
        mxUtils.getValue(sourceCellStyle, mxConstants.STYLE_SHAPE) &&
        (mxUtils.getValue(state.style, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE) != mxConstants.NONE ||
          mxUtils.getValue(state.style, mxConstants.STYLE_FILLCOLOR, mxConstants.NONE) != mxConstants.NONE ||
          mxUtils.getValue(state.style, mxConstants.STYLE_GRADIENTCOLOR, mxConstants.NONE) != mxConstants.NONE)) ||
        mxUtils.getValue(sourceCellStyle, mxConstants.STYLE_SHAPE) == 'image' ||
        timeOnTarget > 1500 ||
        graph.model.isEdge(state.cell)) &&
      timeOnTarget > this.dropTargetDelay &&
      !this.isDropStyleTargetIgnored(state) &&
      ((graph.model.isVertex(state.cell) && firstVertex != null) ||
        (graph.model.isEdge(state.cell) && graph.model.isEdge(cells[0])))
    ) {
      currentStyleTarget = state;
      var tmp = graph.model.isEdge(state.cell)
        ? graph.view.getPoint(state)
        : new mxPoint(state.getCenterX(), state.getCenterY());
      tmp = new mxRectangle(
        tmp.x - this.refreshTarget.width / 2,
        tmp.y - this.refreshTarget.height / 2,
        this.refreshTarget.width,
        this.refreshTarget.height
      );

      styleTarget.style.left = Math.floor(tmp.x) + 'px';
      styleTarget.style.top = Math.floor(tmp.y) + 'px';

      if (styleTargetParent == null) {
        graph.container.appendChild(styleTarget);
        styleTargetParent = styleTarget.parentNode;
      }

      checkArrow(x, y, tmp, styleTarget);
    }
    // Does not reset on ignored edges
    else if (
      currentStyleTarget == null ||
      !mxUtils.contains(currentStyleTarget, x, y) ||
      (timeOnTarget > 1500 && !mxEvent.isShiftDown(evt))
    ) {
      currentStyleTarget = null;

      if (styleTargetParent != null) {
        styleTarget.parentNode.removeChild(styleTarget);
        styleTargetParent = null;
      }
    } else if (currentStyleTarget != null && styleTargetParent != null) {
      // Sets active Arrow as side effect
      var tmp = graph.model.isEdge(currentStyleTarget.cell)
        ? graph.view.getPoint(currentStyleTarget)
        : new mxPoint(currentStyleTarget.getCenterX(), currentStyleTarget.getCenterY());
      tmp = new mxRectangle(
        tmp.x - this.refreshTarget.width / 2,
        tmp.y - this.refreshTarget.height / 2,
        this.refreshTarget.width,
        this.refreshTarget.height
      );
      checkArrow(x, y, tmp, styleTarget);
    }

    // Checks if inside bounds
    if (activeTarget && currentTargetState != null && !mxEvent.isAltDown(evt) && activeArrow == null) {
      // LATER: Use hit-detection for edges
      bbox = mxRectangle.fromRectangle(currentTargetState);

      if (graph.model.isEdge(currentTargetState.cell)) {
        var pts = currentTargetState.absolutePoints;

        if (roundSource.parentNode != null) {
          var p0 = pts[0];
          bbox.add(
            checkArrow(
              x,
              y,
              new mxRectangle(
                p0.x - this.roundDrop.width / 2,
                p0.y - this.roundDrop.height / 2,
                this.roundDrop.width,
                this.roundDrop.height
              ),
              roundSource
            )
          );
        }

        if (roundTarget.parentNode != null) {
          var pe = pts[pts.length - 1];
          bbox.add(
            checkArrow(
              x,
              y,
              new mxRectangle(
                pe.x - this.roundDrop.width / 2,
                pe.y - this.roundDrop.height / 2,
                this.roundDrop.width,
                this.roundDrop.height
              ),
              roundTarget
            )
          );
        }
      } else {
        var bds = mxRectangle.fromRectangle(currentTargetState);

        // Uses outer bounding box to take rotation into account
        if (currentTargetState.shape != null && currentTargetState.shape.boundingBox != null) {
          bds = mxRectangle.fromRectangle(currentTargetState.shape.boundingBox);
        }

        bds.grow(this.graph.tolerance);
        bds.grow(HoverIcons.prototype.arrowSpacing);

        var handler = this.graph.selectionCellsHandler.getHandler(currentTargetState.cell);

        if (handler != null) {
          bds.x -= handler.horizontalOffset / 2;
          bds.y -= handler.verticalOffset / 2;
          bds.width += handler.horizontalOffset;
          bds.height += handler.verticalOffset;

          // Adds bounding box of rotation handle to avoid overlap
          if (
            handler.rotationShape != null &&
            handler.rotationShape.node != null &&
            handler.rotationShape.node.style.visibility != 'hidden' &&
            handler.rotationShape.node.style.display != 'none' &&
            handler.rotationShape.boundingBox != null
          ) {
            bds.add(handler.rotationShape.boundingBox);
          }
        }

        bbox.add(
          checkArrow(
            x,
            y,
            new mxRectangle(
              currentTargetState.getCenterX() - this.triangleUp.width / 2,
              bds.y - this.triangleUp.height,
              this.triangleUp.width,
              this.triangleUp.height
            ),
            arrowUp
          )
        );
        bbox.add(
          checkArrow(
            x,
            y,
            new mxRectangle(
              bds.x + bds.width,
              currentTargetState.getCenterY() - this.triangleRight.height / 2,
              this.triangleRight.width,
              this.triangleRight.height
            ),
            arrowRight
          )
        );
        bbox.add(
          checkArrow(
            x,
            y,
            new mxRectangle(
              currentTargetState.getCenterX() - this.triangleDown.width / 2,
              bds.y + bds.height,
              this.triangleDown.width,
              this.triangleDown.height
            ),
            arrowDown
          )
        );
        bbox.add(
          checkArrow(
            x,
            y,
            new mxRectangle(
              bds.x - this.triangleLeft.width,
              currentTargetState.getCenterY() - this.triangleLeft.height / 2,
              this.triangleLeft.width,
              this.triangleLeft.height
            ),
            arrowLeft
          )
        );
      }

      // Adds tolerance
      if (bbox != null) {
        bbox.grow(10);
      }
    }

    direction = mxConstants.DIRECTION_NORTH;

    if (activeArrow == arrowRight) {
      direction = mxConstants.DIRECTION_EAST;
    } else if (activeArrow == arrowDown || activeArrow == roundTarget) {
      direction = mxConstants.DIRECTION_SOUTH;
    } else if (activeArrow == arrowLeft) {
      direction = mxConstants.DIRECTION_WEST;
    }

    if (currentStyleTarget != null && activeArrow == styleTarget) {
      state = currentStyleTarget;
    }

    var validTarget =
      (firstVertex == null || graph.isCellConnectable(cells[firstVertex])) &&
      ((graph.model.isEdge(cell) && firstVertex != null) ||
        (graph.model.isVertex(cell) && graph.isCellConnectable(cell)));

    // Drop arrows shown after this.dropTargetDelay, hidden after 5 secs, switches arrows after 500ms
    if (
      (currentTargetState != null && timeOnTarget >= 5000) ||
      (currentTargetState != state &&
        (bbox == null || !mxUtils.contains(bbox, x, y) || (timeOnTarget > 500 && activeArrow == null && validTarget)))
    ) {
      activeTarget = false;
      currentTargetState =
        (timeOnTarget < 5000 && timeOnTarget > this.dropTargetDelay) || graph.model.isEdge(cell) ? state : null;

      if (currentTargetState != null && validTarget) {
        var elts = [roundSource, roundTarget, arrowUp, arrowRight, arrowDown, arrowLeft];

        for (var i = 0; i < elts.length; i++) {
          if (elts[i].parentNode != null) {
            elts[i].parentNode.removeChild(elts[i]);
          }
        }

        if (graph.model.isEdge(cell)) {
          var pts = state.absolutePoints;

          if (pts != null) {
            var p0 = pts[0];
            var pe = pts[pts.length - 1];
            var tol = graph.tolerance;
            var box = new mxRectangle(x - tol, y - tol, 2 * tol, 2 * tol);

            roundSource.style.left = Math.floor(p0.x - this.roundDrop.width / 2) + 'px';
            roundSource.style.top = Math.floor(p0.y - this.roundDrop.height / 2) + 'px';

            roundTarget.style.left = Math.floor(pe.x - this.roundDrop.width / 2) + 'px';
            roundTarget.style.top = Math.floor(pe.y - this.roundDrop.height / 2) + 'px';

            if (graph.model.getTerminal(cell, true) == null) {
              graph.container.appendChild(roundSource);
            }

            if (graph.model.getTerminal(cell, false) == null) {
              graph.container.appendChild(roundTarget);
            }
          }
        } else {
          var bds = mxRectangle.fromRectangle(state);

          // Uses outer bounding box to take rotation into account
          if (state.shape != null && state.shape.boundingBox != null) {
            bds = mxRectangle.fromRectangle(state.shape.boundingBox);
          }

          bds.grow(this.graph.tolerance);
          bds.grow(HoverIcons.prototype.arrowSpacing);

          var handler = this.graph.selectionCellsHandler.getHandler(state.cell);

          if (handler != null) {
            bds.x -= handler.horizontalOffset / 2;
            bds.y -= handler.verticalOffset / 2;
            bds.width += handler.horizontalOffset;
            bds.height += handler.verticalOffset;

            // Adds bounding box of rotation handle to avoid overlap
            if (
              handler.rotationShape != null &&
              handler.rotationShape.node != null &&
              handler.rotationShape.node.style.visibility != 'hidden' &&
              handler.rotationShape.node.style.display != 'none' &&
              handler.rotationShape.boundingBox != null
            ) {
              bds.add(handler.rotationShape.boundingBox);
            }
          }

          arrowUp.style.left = Math.floor(state.getCenterX() - this.triangleUp.width / 2) + 'px';
          arrowUp.style.top = Math.floor(bds.y - this.triangleUp.height) + 'px';

          arrowRight.style.left = Math.floor(bds.x + bds.width) + 'px';
          arrowRight.style.top = Math.floor(state.getCenterY() - this.triangleRight.height / 2) + 'px';

          arrowDown.style.left = arrowUp.style.left;
          arrowDown.style.top = Math.floor(bds.y + bds.height) + 'px';

          arrowLeft.style.left = Math.floor(bds.x - this.triangleLeft.width) + 'px';
          arrowLeft.style.top = arrowRight.style.top;

          if (state.style['portConstraint'] != 'eastwest') {
            graph.container.appendChild(arrowUp);
            graph.container.appendChild(arrowDown);
          }

          graph.container.appendChild(arrowRight);
          graph.container.appendChild(arrowLeft);
        }

        // Hides handle for cell under mouse
        if (state != null) {
          currentStateHandle = graph.selectionCellsHandler.getHandler(state.cell);

          if (currentStateHandle != null && currentStateHandle.setHandlesVisible != null) {
            currentStateHandle.setHandlesVisible(false);
          }
        }

        activeTarget = true;
      } else {
        var elts = [roundSource, roundTarget, arrowUp, arrowRight, arrowDown, arrowLeft];

        for (var i = 0; i < elts.length; i++) {
          if (elts[i].parentNode != null) {
            elts[i].parentNode.removeChild(elts[i]);
          }
        }
      }
    }

    if (!activeTarget && currentStateHandle != null) {
      currentStateHandle.setHandlesVisible(true);
    }

    // Handles drop target
    var target =
      (!mxEvent.isAltDown(evt) || mxEvent.isShiftDown(evt)) &&
      !(currentStyleTarget != null && activeArrow == styleTarget)
        ? mxDragSource.prototype.getDropTarget.apply(this, arguments)
        : null;
    var model = graph.getModel();

    if (target != null) {
      if (activeArrow != null || !graph.isSplitTarget(target, cells, evt)) {
        // Selects parent group as drop target
        while (
          target != null &&
          !graph.isValidDropTarget(target, cells, evt) &&
          model.isVertex(model.getParent(target))
        ) {
          target = model.getParent(target);
        }

        if (
          target != null &&
          (graph.view.currentRoot == target ||
            (!graph.isValidRoot(target) && graph.getModel().getChildCount(target) == 0) ||
            graph.isCellLocked(target) ||
            model.isEdge(target) ||
            !graph.isValidDropTarget(target, cells, evt))
        ) {
          target = null;
        }
      }
    }

    return target;
  });

  dragSource.stopDrag = function () {
    mxDragSource.prototype.stopDrag.apply(this, arguments);

    var elts = [roundSource, roundTarget, styleTarget, arrowUp, arrowRight, arrowDown, arrowLeft];

    for (var i = 0; i < elts.length; i++) {
      if (elts[i].parentNode != null) {
        elts[i].parentNode.removeChild(elts[i]);
      }
    }

    if (currentTargetState != null && currentStateHandle != null) {
      currentStateHandle.reset();
    }

    currentStateHandle = null;
    currentTargetState = null;
    currentStyleTarget = null;
    styleTargetParent = null;
    activeArrow = null;
  };

  return dragSource;
};

/**
 * Adds a handler for inserting the cell with a single click.
 */
Sidebar.prototype.itemClicked = function (cells, ds, evt, elt) {
  var graph = this.editorUi.editor.graph;
  graph.container.focus();

  // Alt+Click inserts and connects
  if (mxEvent.isAltDown(evt) && graph.getSelectionCount() == 1 && graph.model.isVertex(graph.getSelectionCell())) {
    var firstVertex = null;

    for (var i = 0; i < cells.length && firstVertex == null; i++) {
      if (graph.model.isVertex(cells[i])) {
        firstVertex = i;
      }
    }

    if (firstVertex != null) {
      graph.setSelectionCells(
        this.dropAndConnect(
          graph.getSelectionCell(),
          cells,
          mxEvent.isMetaDown(evt) || mxEvent.isControlDown(evt)
            ? mxEvent.isShiftDown(evt)
              ? mxConstants.DIRECTION_WEST
              : mxConstants.DIRECTION_NORTH
            : mxEvent.isShiftDown(evt)
            ? mxConstants.DIRECTION_EAST
            : mxConstants.DIRECTION_SOUTH,
          firstVertex,
          evt
        )
      );
      graph.scrollCellToVisible(graph.getSelectionCell());
    }
  }
  // Shift+Click updates shape
  else if (mxEvent.isShiftDown(evt) && !graph.isSelectionEmpty()) {
    this.updateShapes(cells[0], graph.getSelectionCells());
    graph.scrollCellToVisible(graph.getSelectionCell());
  } else {
    var pt = mxEvent.isAltDown(evt)
      ? graph.getFreeInsertPoint()
      : graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry(cells, true));
    ds.drop(graph, evt, null, pt.x, pt.y, true);
  }
};

/**
 * Adds a handler for inserting the cell with a single click.
 */
Sidebar.prototype.addClickHandler = function (elt, ds, cells) {
  var graph = this.editorUi.editor.graph;
  var oldMouseDown = ds.mouseDown;
  var oldMouseMove = ds.mouseMove;
  var oldMouseUp = ds.mouseUp;
  var tol = graph.tolerance;
  var first = null;
  var sb = this;

  ds.mouseDown = function (evt) {
    oldMouseDown.apply(this, arguments);
    first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));

    if (this.dragElement != null) {
      this.dragElement.style.display = 'none';
      mxUtils.setOpacity(elt, 50);
    }
  };

  ds.mouseMove = function (evt) {
    if (
      this.dragElement != null &&
      this.dragElement.style.display == 'none' &&
      first != null &&
      (Math.abs(first.x - mxEvent.getClientX(evt)) > tol || Math.abs(first.y - mxEvent.getClientY(evt)) > tol)
    ) {
      this.dragElement.style.display = '';
      mxUtils.setOpacity(elt, 100);
    }

    oldMouseMove.apply(this, arguments);
  };

  ds.mouseUp = function (evt) {
    try {
      if (
        !mxEvent.isPopupTrigger(evt) &&
        this.currentGraph == null &&
        this.dragElement != null &&
        this.dragElement.style.display == 'none'
      ) {
        sb.itemClicked(cells, ds, evt, elt);
      }

      oldMouseUp.apply(ds, arguments);
      mxUtils.setOpacity(elt, 100);
      first = null;

      // Blocks tooltips on this element after single click
      sb.currentElt = elt;
    } catch (e) {
      ds.reset();
      sb.editorUi.handleError(e);
    }
  };
};

/**
 * Creates a drop handler for inserting the given cells.
 */
Sidebar.prototype.createVertexTemplateEntry = function (
  style,
  width,
  height,
  value,
  title,
  showLabel,
  showTitle,
  tags
) {
  tags = tags != null && tags.length > 0 ? tags : title != null ? title.toLowerCase() : '';

  return this.addEntry(
    tags,
    mxUtils.bind(this, function () {
      return this.createVertexTemplate(style, width, height, value, title, showLabel, showTitle);
    })
  );
};

/**
 * Creates a drop handler for inserting the given cells.
 */
Sidebar.prototype.createVertexTemplate = function (
  style,
  width,
  height,
  value,
  title,
  showLabel,
  showTitle,
  allowCellsInserted
) {
  var cells = [new mxCell(value != null ? value : '', new mxGeometry(0, 0, width, height), style)];
  cells[0].vertex = true;

  return this.createVertexTemplateFromCells(cells, width, height, title, showLabel, showTitle, allowCellsInserted);
};

/**
 * Creates a drop handler for inserting the given cells.
 */
Sidebar.prototype.createVertexTemplateFromData = function (
  data,
  width,
  height,
  title,
  showLabel,
  showTitle,
  allowCellsInserted
) {
  var doc = mxUtils.parseXml(Graph.decompress(data));
  var codec = new mxCodec(doc);

  var model = new mxGraphModel();
  codec.decode(doc.documentElement, model);

  var cells = this.graph.cloneCells(model.root.getChildAt(0).children);

  return this.createVertexTemplateFromCells(cells, width, height, title, showLabel, showTitle, allowCellsInserted);
};

/**
 * Creates a drop handler for inserting the given cells.
 */
Sidebar.prototype.createVertexTemplateFromCells = function (
  cells,
  width,
  height,
  title,
  showLabel,
  showTitle,
  allowCellsInserted
) {
  // Use this line to convert calls to this function with lots of boilerplate code for creating cells
  //console.trace('xml', Graph.compress(mxUtils.getXml(this.graph.encodeCells(cells))), cells);
  return this.createItem(cells, title, showLabel, showTitle, width, height, allowCellsInserted);
};

/**
 *
 */
Sidebar.prototype.createEdgeTemplateEntry = function (
  style,
  width,
  height,
  value,
  title,
  showLabel,
  tags,
  allowCellsInserted
) {
  tags = tags != null && tags.length > 0 ? tags : title.toLowerCase();

  return this.addEntry(
    tags,
    mxUtils.bind(this, function () {
      return this.createEdgeTemplate(style, width, height, value, title, showLabel, allowCellsInserted);
    })
  );
};

/**
 * Creates a drop handler for inserting the given cells.
 */
Sidebar.prototype.createEdgeTemplate = function (style, width, height, value, title, showLabel, allowCellsInserted) {
  var cell = new mxCell(value != null ? value : '', new mxGeometry(0, 0, width, height), style);
  cell.geometry.setTerminalPoint(new mxPoint(0, height), true);
  cell.geometry.setTerminalPoint(new mxPoint(width, 0), false);
  cell.geometry.relative = true;
  cell.edge = true;

  return this.createEdgeTemplateFromCells([cell], width, height, title, showLabel, allowCellsInserted);
};

/**
 * Creates a drop handler for inserting the given cells.
 */
Sidebar.prototype.createEdgeTemplateFromCells = function (cells, width, height, title, showLabel, allowCellsInserted) {
  return this.createItem(cells, title, showLabel, true, width, height, allowCellsInserted);
};

/**
 * Adds the given palette.
 */
Sidebar.prototype.addPaletteFunctions = function (id, title, expanded, fns) {
  this.addPalette(
    id,
    title,
    expanded,
    mxUtils.bind(this, function (content) {
      for (var i = 0; i < fns.length; i++) {
        content.appendChild(fns[i](content));
      }
    })
  );
};

/**
 * Adds the given palette.
 */
Sidebar.prototype.addPalette = function (id, title, expanded, onInit) {
  var elt = this.createTitle(title);
  this.container.appendChild(elt);

  var div = document.createElement('div');
  div.className = 'geSidebar';

  // Disables built-in pan and zoom in IE10 and later
  if (mxClient.IS_POINTER) {
    div.style.touchAction = 'none';
  }

  if (expanded) {
    onInit(div);
    onInit = null;
  } else {
    div.style.display = 'none';
  }

  this.addFoldingHandler(elt, div, onInit);

  var outer = document.createElement('div');
  outer.appendChild(div);
  this.container.appendChild(outer);

  // Keeps references to the DOM nodes
  if (id != null) {
    this.palettes[id] = [elt, outer];
  }

  return div;
};

/**
 * Create the given title element.
 */
Sidebar.prototype.addFoldingHandler = function (title, content, funct) {
  var initialized = false;

  // Avoids mixed content warning in IE6-8
  if (!mxClient.IS_IE || document.documentMode >= 8) {
    title.style.backgroundImage =
      content.style.display == 'none' ? "url('" + this.collapsedImage + "')" : "url('" + this.expandedImage + "')";
  }

  title.style.backgroundRepeat = 'no-repeat';
  title.style.backgroundPosition = '0% 50%';

  mxEvent.addListener(
    title,
    'click',
    mxUtils.bind(this, function (evt) {
      if (content.style.display == 'none') {
        if (!initialized) {
          initialized = true;

          if (funct != null) {
            // Wait cursor does not show up on Mac
            title.style.cursor = 'wait';
            var prev = title.innerHTML;
            title.innerHTML = mxResources.get('loading') + '...';

            window.setTimeout(
              function () {
                content.style.display = 'block';
                title.style.cursor = '';
                title.innerHTML = prev;

                var fo = mxClient.NO_FO;
                mxClient.NO_FO = Editor.prototype.originalNoForeignObject;
                funct(content, title);
                mxClient.NO_FO = fo;
              },
              mxClient.IS_FF ? 20 : 0
            );
          } else {
            content.style.display = 'block';
          }
        } else {
          content.style.display = 'block';
        }

        title.style.backgroundImage = "url('" + this.expandedImage + "')";
      } else {
        title.style.backgroundImage = "url('" + this.collapsedImage + "')";
        content.style.display = 'none';
      }

      mxEvent.consume(evt);
    })
  );

  // Prevents focus
  if (!mxClient.IS_QUIRKS) {
    mxEvent.addListener(
      title,
      mxClient.IS_POINTER ? 'pointerdown' : 'mousedown',
      mxUtils.bind(this, function (evt) {
        evt.preventDefault();
      })
    );
  }
};

/**
 * Removes the palette for the given ID.
 */
Sidebar.prototype.removePalette = function (id) {
  var elts = this.palettes[id];

  if (elts != null) {
    this.palettes[id] = null;

    for (var i = 0; i < elts.length; i++) {
      this.container.removeChild(elts[i]);
    }

    return true;
  }

  return false;
};

/**
 * Adds the given image palette.
 */
Sidebar.prototype.addImagePalette = function (id, title, prefix, postfix, items, titles, tags) {
  var showTitles = titles != null;
  var fns = [];

  for (var i = 0; i < items.length; i++) {
    mxUtils.bind(this, function (item, title, tmpTags) {
      if (tmpTags == null) {
        var slash = item.lastIndexOf('/');
        var dot = item.lastIndexOf('.');
        tmpTags = item.substring(slash >= 0 ? slash + 1 : 0, dot >= 0 ? dot : item.length).replace(/[-_]/g, ' ');
      }

      fns.push(
        this.createVertexTemplateEntry(
          'image;html=1;image=' + prefix + item + postfix,
          this.defaultImageWidth,
          this.defaultImageHeight,
          '',
          title,
          title != null,
          null,
          this.filterTags(tmpTags)
        )
      );
    })(items[i], titles != null ? titles[i] : null, tags != null ? tags[items[i]] : null);
  }

  this.addPaletteFunctions(id, title, false, fns);
};

/**
 * Creates the array of tags for the given stencil. Duplicates are allowed and will be filtered out later.
 */
Sidebar.prototype.getTagsForStencil = function (packageName, stencilName, moreTags) {
  var tags = packageName.split('.');

  for (var i = 1; i < tags.length; i++) {
    tags[i] = tags[i].replace(/_/g, ' ');
  }

  tags.push(stencilName.replace(/_/g, ' '));

  if (moreTags != null) {
    tags.push(moreTags);
  }

  return tags.slice(1, tags.length);
};

/**
 * Adds the given stencil palette.
 */
Sidebar.prototype.addStencilPalette = function (
  id,
  title,
  stencilFile,
  style,
  ignore,
  onInit,
  scale,
  tags,
  customFns,
  groupId
) {
  scale = scale != null ? scale : 1;

  if (this.addStencilsToIndex) {
    // LATER: Handle asynchronous loading dependency
    var fns = [];

    if (customFns != null) {
      for (var i = 0; i < customFns.length; i++) {
        fns.push(customFns[i]);
      }
    }

    mxStencilRegistry.loadStencilSet(
      stencilFile,
      mxUtils.bind(this, function (packageName, stencilName, displayName, w, h) {
        if (ignore == null || mxUtils.indexOf(ignore, stencilName) < 0) {
          var tmp = this.getTagsForStencil(packageName, stencilName);
          var tmpTags = tags != null ? tags[stencilName] : null;

          if (tmpTags != null) {
            tmp.push(tmpTags);
          }

          fns.push(
            this.createVertexTemplateEntry(
              'shape=' + packageName + stencilName.toLowerCase() + style,
              Math.round(w * scale),
              Math.round(h * scale),
              '',
              stencilName.replace(/_/g, ' '),
              null,
              null,
              this.filterTags(tmp.join(' '))
            )
          );
        }
      }),
      true,
      true
    );

    this.addPaletteFunctions(id, title, false, fns);
  } else {
    this.addPalette(
      id,
      title,
      false,
      mxUtils.bind(this, function (content) {
        if (style == null) {
          style = '';
        }

        if (onInit != null) {
          onInit.call(this, content);
        }

        if (customFns != null) {
          for (var i = 0; i < customFns.length; i++) {
            customFns[i](content);
          }
        }

        mxStencilRegistry.loadStencilSet(
          stencilFile,
          mxUtils.bind(this, function (packageName, stencilName, displayName, w, h) {
            if (ignore == null || mxUtils.indexOf(ignore, stencilName) < 0) {
              content.appendChild(
                this.createVertexTemplate(
                  'shape=' + packageName + stencilName.toLowerCase() + style,
                  Math.round(w * scale),
                  Math.round(h * scale),
                  '',
                  stencilName.replace(/_/g, ' '),
                  true
                )
              );
            }
          }),
          true
        );
      })
    );
  }
};

/**
 * Adds the given stencil palette.
 */
Sidebar.prototype.destroy = function () {
  if (this.graph != null) {
    if (this.graph.container != null && this.graph.container.parentNode != null) {
      this.graph.container.parentNode.removeChild(this.graph.container);
    }

    this.graph.destroy();
    this.graph = null;
  }

  if (this.pointerUpHandler != null) {
    mxEvent.removeListener(document, mxClient.IS_POINTER ? 'pointerup' : 'mouseup', this.pointerUpHandler);
    this.pointerUpHandler = null;
  }

  if (this.pointerDownHandler != null) {
    mxEvent.removeListener(document, mxClient.IS_POINTER ? 'pointerdown' : 'mousedown', this.pointerDownHandler);
    this.pointerDownHandler = null;
  }

  if (this.pointerMoveHandler != null) {
    mxEvent.removeListener(document, mxClient.IS_POINTER ? 'pointermove' : 'mousemove', this.pointerMoveHandler);
    this.pointerMoveHandler = null;
  }

  if (this.pointerOutHandler != null) {
    mxEvent.removeListener(document, mxClient.IS_POINTER ? 'pointerout' : 'mouseout', this.pointerOutHandler);
    this.pointerOutHandler = null;
  }
};
/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
// Workaround for allowing target="_blank" in HTML sanitizer
// see https://code.google.com/p/google-caja/issues/detail?can=2&q=&colspec=ID%20Type%20Status%20Priority%20Owner%20Summary&groupby=&sort=&id=1296
if (typeof html4 !== 'undefined') {
  html4.ATTRIBS['a::target'] = 0;
  html4.ATTRIBS['source::src'] = 0;
  html4.ATTRIBS['video::src'] = 0;
  // Would be nice for tooltips but probably a security risk...
  //html4.ATTRIBS['video::autoplay'] = 0;
  //html4.ATTRIBS['video::autobuffer'] = 0;
}

// Workaround for handling named HTML entities in mxUtils.parseXml
// LATER: How to configure DOMParser to just ignore all entities?
(function () {
  var entities = [
    ['nbsp', '160'],
    ['shy', '173'],
  ];

  var parseXml = mxUtils.parseXml;

  mxUtils.parseXml = function (text) {
    for (var i = 0; i < entities.length; i++) {
      text = text.replace(new RegExp('&' + entities[i][0] + ';', 'g'), '&#' + entities[i][1] + ';');
    }

    return parseXml(text);
  };
})();

// Shim for missing toISOString in older versions of IE
// See https://stackoverflow.com/questions/12907862
if (!Date.prototype.toISOString) {
  (function () {
    function pad(number) {
      var r = String(number);

      if (r.length === 1) {
        r = '0' + r;
      }

      return r;
    }

    Date.prototype.toISOString = function () {
      return (
        this.getUTCFullYear() +
        '-' +
        pad(this.getUTCMonth() + 1) +
        '-' +
        pad(this.getUTCDate()) +
        'T' +
        pad(this.getUTCHours()) +
        ':' +
        pad(this.getUTCMinutes()) +
        ':' +
        pad(this.getUTCSeconds()) +
        '.' +
        String((this.getUTCMilliseconds() / 1000).toFixed(3)).slice(2, 5) +
        'Z'
      );
    };
  })();
}

// Shim for Date.now()
if (!Date.now) {
  Date.now = function () {
    return new Date().getTime();
  };
}

// Changes default colors
/**
 * Measurements Units
 */
mxConstants.POINTS = 1;
mxConstants.MILLIMETERS = 2;
mxConstants.INCHES = 3;
/**
 * This ratio is with page scale 1
 */
mxConstants.PIXELS_PER_MM = 3.937;
mxConstants.PIXELS_PER_INCH = 100;

mxConstants.SHADOW_OPACITY = 0.25;
mxConstants.SHADOWCOLOR = '#000000';
mxConstants.VML_SHADOWCOLOR = '#d0d0d0';
mxGraph.prototype.pageBreakColor = '#c0c0c0';
mxGraph.prototype.pageScale = 1;

// Letter page format is default in US, Canada and Mexico
(function () {
  try {
    if (navigator != null && navigator.language != null) {
      var lang = navigator.language.toLowerCase();
      mxGraph.prototype.pageFormat =
        lang === 'en-us' || lang === 'en-ca' || lang === 'es-mx'
          ? mxConstants.PAGE_FORMAT_LETTER_PORTRAIT
          : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
    }
  } catch (e) {
    // ignore
  }
})();

// Matches label positions of mxGraph 1.x
mxText.prototype.baseSpacingTop = 5;
mxText.prototype.baseSpacingBottom = 1;

// Keeps edges between relative child cells inside parent
mxGraphModel.prototype.ignoreRelativeEdgeParent = false;

// Defines grid properties
mxGraphView.prototype.gridImage = mxClient.IS_SVG
  ? 'data:image/gif;base64,R0lGODlhCgAKAJEAAAAAAP///8zMzP///yH5BAEAAAMALAAAAAAKAAoAAAIJ1I6py+0Po2wFADs='
  : IMAGE_PATH + '/grid.gif';
mxGraphView.prototype.gridSteps = 4;
mxGraphView.prototype.minGridSize = 4;

// UrlParams is null in embed mode
mxGraphView.prototype.defaultGridColor = '#d0d0d0';
mxGraphView.prototype.gridColor = mxGraphView.prototype.defaultGridColor;

//Units
mxGraphView.prototype.unit = mxConstants.POINTS;

mxGraphView.prototype.setUnit = function (unit) {
  if (this.unit != unit) {
    this.unit = unit;

    this.fireEvent(new mxEventObject('unitChanged', 'unit', unit));
  }
};

// Alternative text for unsupported foreignObjects
mxSvgCanvas2D.prototype.foAltText = '[Not supported by viewer]';

// Hook for custom constraints
mxShape.prototype.getConstraints = function (style, w, h) {
  return null;
};

/**
 * Constructs a new graph instance. Note that the constructor does not take a
 * container because the graph instance is needed for creating the UI, which
 * in turn will create the container for the graph. Hence, the container is
 * assigned later in EditorUi.
 */
/**
 * Defines graph class.
 */
window.Graph = function (container, model, renderHint, stylesheet, themes, standalone) {
  mxGraph.call(this, container, model, renderHint, stylesheet);

  this.themes = themes || this.defaultThemes;
  this.currentEdgeStyle = mxUtils.clone(this.defaultEdgeStyle);
  this.currentVertexStyle = mxUtils.clone(this.defaultVertexStyle);
  this.standalone = standalone != null ? standalone : false;

  // Sets the base domain URL and domain path URL for relative links.
  var b = this.baseUrl;
  var p = b.indexOf('//');
  this.domainUrl = '';
  this.domainPathUrl = '';

  if (p > 0) {
    var d = b.indexOf('/', p + 2);

    if (d > 0) {
      this.domainUrl = b.substring(0, d);
    }

    d = b.lastIndexOf('/');

    if (d > 0) {
      this.domainPathUrl = b.substring(0, d + 1);
    }
  }

  // Adds support for HTML labels via style. Note: Currently, only the Java
  // backend supports HTML labels but CSS support is limited to the following:
  // http://docs.oracle.com/javase/6/docs/api/index.html?javax/swing/text/html/CSS.html
  // TODO: Wrap should not affect isHtmlLabel output (should be handled later)
  this.isHtmlLabel = function (cell) {
    var style = this.getCurrentCellStyle(cell);

    return style != null ? style['html'] == '1' || style[mxConstants.STYLE_WHITE_SPACE] == 'wrap' : false;
  };

  // Implements a listener for hover and click handling on edges
  if (this.edgeMode) {
    var start = {
      point: null,
      event: null,
      state: null,
      handle: null,
      selected: false,
    };

    // Uses this event to process mouseDown to check the selection state before it is changed
    this.addListener(
      mxEvent.FIRE_MOUSE_EVENT,
      mxUtils.bind(this, function (sender, evt) {
        if (evt.getProperty('eventName') == 'mouseDown' && this.isEnabled()) {
          var me = evt.getProperty('event');
          var state = me.getState();

          if (!mxEvent.isAltDown(me.getEvent()) && state != null) {
            // Checks if state was removed in call to stopEditing above
            if (this.model.isEdge(state.cell)) {
              start.point = new mxPoint(me.getGraphX(), me.getGraphY());
              start.selected = this.isCellSelected(state.cell);
              start.state = state;
              start.event = me;

              if (
                state.text != null &&
                state.text.boundingBox != null &&
                mxUtils.contains(state.text.boundingBox, me.getGraphX(), me.getGraphY())
              ) {
                start.handle = mxEvent.LABEL_HANDLE;
              } else {
                var handler = this.selectionCellsHandler.getHandler(state.cell);

                if (handler != null && handler.bends != null && handler.bends.length > 0) {
                  start.handle = handler.getHandleForEvent(me);
                }
              }
            } else if (!this.panningHandler.isActive() && !mxEvent.isControlDown(me.getEvent())) {
              var handler = this.selectionCellsHandler.getHandler(state.cell);

              // Cell handles have precedence over row and col resize
              if (handler == null || handler.getHandleForEvent(me) == null) {
                var box = new mxRectangle(me.getGraphX() - 1, me.getGraphY() - 1);
                box.grow(
                  mxEvent.isTouchEvent(me.getEvent())
                    ? mxShape.prototype.svgStrokeTolerance - 1
                    : (mxShape.prototype.svgStrokeTolerance + 1) / 2
                );

                if (this.isTableCell(state.cell) && !this.isCellSelected(state.cell)) {
                  var row = this.model.getParent(state.cell);
                  var table = this.model.getParent(row);

                  if (!this.isCellSelected(table)) {
                    if (
                      (mxUtils.intersects(box, new mxRectangle(state.x, state.y - 2, state.width, 3)) &&
                        this.model.getChildAt(table, 0) != row) ||
                      mxUtils.intersects(box, new mxRectangle(state.x, state.y + state.height - 2, state.width, 3)) ||
                      (mxUtils.intersects(box, new mxRectangle(state.x - 2, state.y, 2, state.height)) &&
                        this.model.getChildAt(row, 0) != state.cell) ||
                      mxUtils.intersects(box, new mxRectangle(state.x + state.width - 2, state.y, 2, state.height))
                    ) {
                      var wasSelected = this.selectionCellsHandler.isHandled(table);
                      this.selectCellForEvent(table, me.getEvent());
                      handler = this.selectionCellsHandler.getHandler(table);

                      if (handler != null) {
                        var handle = handler.getHandleForEvent(me);

                        if (handle != null) {
                          handler.start(me.getGraphX(), me.getGraphY(), handle);
                          handler.blockDelayedSelection = !wasSelected;
                          me.consume();
                        }
                      }
                    }
                  }
                }

                // Hover for swimlane start sizes inside tables
                var current = state;

                while (
                  !me.isConsumed() &&
                  current != null &&
                  (this.isTableCell(current.cell) || this.isTableRow(current.cell) || this.isTable(current.cell))
                ) {
                  if (this.isSwimlane(current.cell)) {
                    var offset = this.getActualStartSize(current.cell);
                    var s = this.view.scale;

                    if (
                      ((offset.x > 0 || offset.width > 0) &&
                        mxUtils.intersects(
                          box,
                          new mxRectangle(
                            current.x + (offset.x - offset.width - 1) * s + (offset.x == 0 ? current.width : 0),
                            current.y,
                            1,
                            current.height
                          )
                        )) ||
                      ((offset.y > 0 || offset.height > 0) &&
                        mxUtils.intersects(
                          box,
                          new mxRectangle(
                            current.x,
                            current.y + (offset.y - offset.height - 1) * s + (offset.y == 0 ? current.height : 0),
                            current.width,
                            1
                          )
                        ))
                    ) {
                      this.selectCellForEvent(current.cell, me.getEvent());
                      handler = this.selectionCellsHandler.getHandler(current.cell);

                      if (handler != null) {
                        // Swimlane start size handle is last custom handle
                        var handle = mxEvent.CUSTOM_HANDLE - handler.customHandles.length + 1;
                        handler.start(me.getGraphX(), me.getGraphY(), handle);
                        me.consume();
                      }
                    }
                  }

                  current = this.view.getState(this.model.getParent(current.cell));
                }
              }
            }
          }
        }
      })
    );

    var mouseDown = null;

    this.addMouseListener({
      mouseDown: function (sender, me) {},
      mouseMove: mxUtils.bind(this, function (sender, me) {
        // Checks if any other handler is active
        var handlerMap = this.selectionCellsHandler.handlers.map;

        for (var key in handlerMap) {
          if (handlerMap[key].index != null) {
            return;
          }
        }

        if (this.isEnabled() && !this.panningHandler.isActive() && !mxEvent.isAltDown(me.getEvent())) {
          var tol = this.tolerance;

          if (start.point != null && start.state != null && start.event != null) {
            var state = start.state;

            if (Math.abs(start.point.x - me.getGraphX()) > tol || Math.abs(start.point.y - me.getGraphY()) > tol) {
              var handler = this.selectionCellsHandler.getHandler(state.cell);

              if (handler == null && this.model.isEdge(state.cell)) {
                handler = this.createHandler(state);
              }

              if (handler != null && handler.bends != null && handler.bends.length > 0) {
                var handle = handler.getHandleForEvent(start.event);
                var edgeStyle = this.view.getEdgeStyle(state);
                var entity = edgeStyle == mxEdgeStyle.EntityRelation;

                // Handles special case where label was clicked on unselected edge in which
                // case the label will be moved regardless of the handle that is returned
                if (!start.selected && start.handle == mxEvent.LABEL_HANDLE) {
                  handle = start.handle;
                }

                if (!entity || handle == 0 || handle == handler.bends.length - 1 || handle == mxEvent.LABEL_HANDLE) {
                  // Source or target handle or connected for direct handle access or orthogonal line
                  // with just two points where the central handle is moved regardless of mouse position
                  if (
                    handle == mxEvent.LABEL_HANDLE ||
                    handle == 0 ||
                    state.visibleSourceState != null ||
                    handle == handler.bends.length - 1 ||
                    state.visibleTargetState != null
                  ) {
                    if (!entity && handle != mxEvent.LABEL_HANDLE) {
                      var pts = state.absolutePoints;

                      // Default case where handles are at corner points handles
                      // drag of corner as drag of existing point
                      if (
                        pts != null &&
                        ((edgeStyle == null && handle == null) || edgeStyle == mxEdgeStyle.OrthConnector)
                      ) {
                        // Does not use handles if they were not initially visible
                        handle = start.handle;

                        if (handle == null) {
                          var box = new mxRectangle(start.point.x, start.point.y);
                          box.grow(mxEdgeHandler.prototype.handleImage.width / 2);

                          if (mxUtils.contains(box, pts[0].x, pts[0].y)) {
                            // Moves source terminal handle
                            handle = 0;
                          } else if (mxUtils.contains(box, pts[pts.length - 1].x, pts[pts.length - 1].y)) {
                            // Moves target terminal handle
                            handle = handler.bends.length - 1;
                          } else {
                            // Checks if edge has no bends
                            var nobends =
                              edgeStyle != null &&
                              (pts.length == 2 ||
                                (pts.length == 3 &&
                                  ((Math.round(pts[0].x - pts[1].x) == 0 && Math.round(pts[1].x - pts[2].x) == 0) ||
                                    (Math.round(pts[0].y - pts[1].y) == 0 && Math.round(pts[1].y - pts[2].y) == 0))));

                            if (nobends) {
                              // Moves central handle for straight orthogonal edges
                              handle = 2;
                            } else {
                              // Finds and moves vertical or horizontal segment
                              handle = mxUtils.findNearestSegment(state, start.point.x, start.point.y);

                              // Converts segment to virtual handle index
                              if (edgeStyle == null) {
                                handle = mxEvent.VIRTUAL_HANDLE - handle;
                              }
                              // Maps segment to handle
                              else {
                                handle += 1;
                              }
                            }
                          }
                        }
                      }

                      // Creates a new waypoint and starts moving it
                      if (handle == null) {
                        handle = mxEvent.VIRTUAL_HANDLE;
                      }
                    }

                    handler.start(me.getGraphX(), me.getGraphX(), handle);
                    me.consume();

                    // Removes preview rectangle in graph handler
                    this.graphHandler.reset();
                  }
                } else if (entity && (state.visibleSourceState != null || state.visibleTargetState != null)) {
                  // Disables moves on entity to make it consistent
                  this.graphHandler.reset();
                  me.consume();
                }
              }

              if (handler != null) {
                // Lazy selection for edges inside groups
                if (this.selectionCellsHandler.isHandlerActive(handler)) {
                  if (!this.isCellSelected(state.cell)) {
                    this.selectionCellsHandler.handlers.put(state.cell, handler);
                    this.selectCellForEvent(state.cell, me.getEvent());
                  }
                } else if (!this.isCellSelected(state.cell)) {
                  // Destroy temporary handler
                  handler.destroy();
                }
              }

              // Reset start state
              start.selected = false;
              start.handle = null;
              start.state = null;
              start.event = null;
              start.point = null;
            }
          } else {
            // Updates cursor for unselected edges under the mouse
            var state = me.getState();

            if (state != null) {
              var cursor = null;

              // Checks if state was removed in call to stopEditing above
              if (this.model.isEdge(state.cell)) {
                var box = new mxRectangle(me.getGraphX(), me.getGraphY());
                box.grow(mxEdgeHandler.prototype.handleImage.width / 2);
                var pts = state.absolutePoints;

                if (pts != null) {
                  if (
                    state.text != null &&
                    state.text.boundingBox != null &&
                    mxUtils.contains(state.text.boundingBox, me.getGraphX(), me.getGraphY())
                  ) {
                    cursor = 'move';
                  } else if (
                    mxUtils.contains(box, pts[0].x, pts[0].y) ||
                    mxUtils.contains(box, pts[pts.length - 1].x, pts[pts.length - 1].y)
                  ) {
                    cursor = 'pointer';
                  } else if (state.visibleSourceState != null || state.visibleTargetState != null) {
                    // Moving is not allowed for entity relation but still indicate hover state
                    var tmp = this.view.getEdgeStyle(state);
                    cursor = 'crosshair';

                    if (tmp != mxEdgeStyle.EntityRelation && this.isOrthogonal(state)) {
                      var idx = mxUtils.findNearestSegment(state, me.getGraphX(), me.getGraphY());

                      if (idx < pts.length - 1 && idx >= 0) {
                        cursor = Math.round(pts[idx].x - pts[idx + 1].x) == 0 ? 'col-resize' : 'row-resize';
                      }
                    }
                  }
                }
              } else if (!mxEvent.isControlDown(me.getEvent())) {
                var box = new mxRectangle(me.getGraphX() - 1, me.getGraphY() - 1);
                box.grow(mxShape.prototype.svgStrokeTolerance / 2);

                if (this.isTableCell(state.cell)) {
                  var row = this.model.getParent(state.cell);
                  var table = this.model.getParent(row);

                  if (!this.isCellSelected(table)) {
                    if (
                      (mxUtils.intersects(box, new mxRectangle(state.x - 2, state.y, 2, state.height)) &&
                        this.model.getChildAt(row, 0) != state.cell) ||
                      mxUtils.intersects(box, new mxRectangle(state.x + state.width - 2, state.y, 2, state.height))
                    ) {
                      cursor = 'col-resize';
                    } else if (
                      (mxUtils.intersects(box, new mxRectangle(state.x, state.y - 2, state.width, 3)) &&
                        this.model.getChildAt(table, 0) != row) ||
                      mxUtils.intersects(box, new mxRectangle(state.x, state.y + state.height - 2, state.width, 3))
                    ) {
                      cursor = 'row-resize';
                    }
                  }
                }

                // Hover for swimlane start sizes inside tables
                var current = state;

                while (
                  cursor == null &&
                  current != null &&
                  (this.isTableCell(current.cell) || this.isTableRow(current.cell) || this.isTable(current.cell))
                ) {
                  if (this.isSwimlane(current.cell)) {
                    var offset = this.getActualStartSize(current.cell);
                    var s = this.view.scale;

                    if (
                      (offset.x > 0 || offset.width > 0) &&
                      mxUtils.intersects(
                        box,
                        new mxRectangle(
                          current.x + (offset.x - offset.width - 1) * s + (offset.x == 0 ? current.width * s : 0),
                          current.y,
                          1,
                          current.height
                        )
                      )
                    ) {
                      cursor = 'col-resize';
                    } else if (
                      (offset.y > 0 || offset.height > 0) &&
                      mxUtils.intersects(
                        box,
                        new mxRectangle(
                          current.x,
                          current.y + (offset.y - offset.height - 1) * s + (offset.y == 0 ? current.height : 0),
                          current.width,
                          1
                        )
                      )
                    ) {
                      cursor = 'row-resize';
                    }
                  }

                  current = this.view.getState(this.model.getParent(current.cell));
                }
              }

              if (cursor != null) {
                state.setCursor(cursor);
              }
            }
          }
        }
      }),
      mouseUp: mxUtils.bind(this, function (sender, me) {
        start.state = null;
        start.event = null;
        start.point = null;
        start.handle = null;
      }),
    });
  }

  // HTML entities are displayed as plain text in wrapped plain text labels
  this.cellRenderer.getLabelValue = function (state) {
    var result = mxCellRenderer.prototype.getLabelValue.apply(this, arguments);

    if (state.view.graph.isHtmlLabel(state.cell)) {
      if (state.style['html'] != 1) {
        result = mxUtils.htmlEntities(result, false);
      } else {
        result = state.view.graph.sanitizeHtml(result);
      }
    }

    return result;
  };

  // All code below not available and not needed in embed mode
  if (typeof mxVertexHandler !== 'undefined') {
    this.setConnectable(true);
    this.setDropEnabled(true);
    this.setPanning(true);
    this.setTooltips(true);
    this.setAllowLoops(true);
    this.allowAutoPanning = true;
    this.resetEdgesOnConnect = false;
    this.constrainChildren = false;
    this.constrainRelativeChildren = true;

    // Do not scroll after moving cells
    this.graphHandler.scrollOnMove = false;
    this.graphHandler.scaleGrid = true;

    // Disables cloning of connection sources by default
    this.connectionHandler.setCreateTarget(false);
    this.connectionHandler.insertBeforeSource = true;

    // Disables built-in connection starts
    this.connectionHandler.isValidSource = function (cell, me) {
      return false;
    };

    // Sets the style to be used when an elbow edge is double clicked
    this.alternateEdgeStyle = 'vertical';

    if (stylesheet == null) {
      this.loadStylesheet();
    }

    // Adds page centers to the guides for moving cells
    var graphHandlerGetGuideStates = this.graphHandler.getGuideStates;
    this.graphHandler.getGuideStates = function () {
      var result = graphHandlerGetGuideStates.apply(this, arguments);

      // Create virtual cell state for page centers
      if (this.graph.pageVisible) {
        var guides = [];

        var pf = this.graph.pageFormat;
        var ps = this.graph.pageScale;
        var pw = pf.width * ps;
        var ph = pf.height * ps;
        var t = this.graph.view.translate;
        var s = this.graph.view.scale;

        var layout = this.graph.getPageLayout();

        for (var i = 0; i < layout.width; i++) {
          guides.push(new mxRectangle(((layout.x + i) * pw + t.x) * s, (layout.y * ph + t.y) * s, pw * s, ph * s));
        }

        for (var j = 1; j < layout.height; j++) {
          guides.push(new mxRectangle((layout.x * pw + t.x) * s, ((layout.y + j) * ph + t.y) * s, pw * s, ph * s));
        }

        // Page center guides have precedence over normal guides
        result = guides.concat(result);
      }

      return result;
    };

    // Overrides zIndex for dragElement
    mxDragSource.prototype.dragElementZIndex = mxPopupMenu.prototype.zIndex;

    // Overrides color for virtual guides for page centers
    mxGuide.prototype.getGuideColor = function (state, horizontal) {
      return state.cell == null ? '#ffa500' /* orange */ : mxConstants.GUIDE_COLOR;
    };

    // Changes color of move preview for black backgrounds
    this.graphHandler.createPreviewShape = function (bounds) {
      this.previewColor = this.graph.background == '#000000' ? '#ffffff' : mxGraphHandler.prototype.previewColor;

      return mxGraphHandler.prototype.createPreviewShape.apply(this, arguments);
    };

    // Handles parts of cells by checking if part=1 is in the style and returning the parent
    // if the parent is not already in the list of cells. container style is used to disable
    // step into swimlanes and dropTarget style is used to disable acting as a drop target.
    // LATER: Handle recursive parts
    var graphHandlerGetCells = this.graphHandler.getCells;

    this.graphHandler.getCells = function (initialCell) {
      var cells = graphHandlerGetCells.apply(this, arguments);
      var lookup = new mxDictionary();
      var newCells = [];

      for (var i = 0; i < cells.length; i++) {
        // Propagates to composite parents or moves selected table rows
        var cell =
          this.graph.isTableCell(initialCell) && this.graph.isTableCell(cells[i]) && this.graph.isCellSelected(cells[i])
            ? this.graph.model.getParent(cells[i])
            : this.graph.isTableRow(initialCell) &&
              this.graph.isTableRow(cells[i]) &&
              this.graph.isCellSelected(cells[i])
            ? cells[i]
            : this.graph.getCompositeParent(cells[i]);

        if (cell != null && !lookup.get(cell)) {
          lookup.put(cell, true);
          newCells.push(cell);
        }
      }

      return newCells;
    };

    // Handles parts and selected rows in tables of cells for drag and drop
    var graphHandlerStart = this.graphHandler.start;

    this.graphHandler.start = function (cell, x, y, cells) {
      // Propagates to selected table row to start move
      var ignoreParent = false;

      if (this.graph.isTableCell(cell)) {
        if (!this.graph.isCellSelected(cell)) {
          cell = this.graph.model.getParent(cell);
        } else {
          ignoreParent = true;
        }
      }

      if (!ignoreParent && (!this.graph.isTableRow(cell) || !this.graph.isCellSelected(cell))) {
        cell = this.graph.getCompositeParent(cell);
      }

      graphHandlerStart.apply(this, arguments);
    };

    // Handles parts of cells when cloning the source for new connections
    this.connectionHandler.createTargetVertex = function (evt, source) {
      source = this.graph.getCompositeParent(source);

      return mxConnectionHandler.prototype.createTargetVertex.apply(this, arguments);
    };

    var rubberband = new mxRubberband(this);

    this.getRubberband = function () {
      return rubberband;
    };

    // Timer-based activation of outline connect in connection handler
    var startTime = new Date().getTime();
    var timeOnTarget = 0;

    var connectionHandlerMouseMove = this.connectionHandler.mouseMove;

    this.connectionHandler.mouseMove = function () {
      var prev = this.currentState;
      connectionHandlerMouseMove.apply(this, arguments);

      if (prev != this.currentState) {
        startTime = new Date().getTime();
        timeOnTarget = 0;
      } else {
        timeOnTarget = new Date().getTime() - startTime;
      }
    };

    // Activates outline connect after 1500ms with touch event or if alt is pressed inside the shape
    // outlineConnect=0 is a custom style that means do not connect to strokes inside the shape,
    // or in other words, connect to the shape's perimeter if the highlight is under the mouse
    // (the name is because the highlight, including all strokes, is called outline in the code)
    var connectionHandleIsOutlineConnectEvent = this.connectionHandler.isOutlineConnectEvent;

    this.connectionHandler.isOutlineConnectEvent = function (me) {
      return (
        (this.currentState != null && me.getState() == this.currentState && timeOnTarget > 2000) ||
        ((this.currentState == null || mxUtils.getValue(this.currentState.style, 'outlineConnect', '1') != '0') &&
          connectionHandleIsOutlineConnectEvent.apply(this, arguments))
      );
    };

    // Adds shift+click to toggle selection state
    var isToggleEvent = this.isToggleEvent;
    this.isToggleEvent = function (evt) {
      return isToggleEvent.apply(this, arguments) || (!mxClient.IS_CHROMEOS && mxEvent.isShiftDown(evt));
    };

    // Workaround for Firefox where first mouse down is received
    // after tap and hold if scrollbars are visible, which means
    // start rubberband immediately if no cell is under mouse.
    var isForceRubberBandEvent = rubberband.isForceRubberbandEvent;
    rubberband.isForceRubberbandEvent = function (me) {
      return (
        (isForceRubberBandEvent.apply(this, arguments) &&
          !mxEvent.isShiftDown(me.getEvent()) &&
          !mxEvent.isControlDown(me.getEvent())) ||
        (mxClient.IS_CHROMEOS && mxEvent.isShiftDown(me.getEvent())) ||
        (mxUtils.hasScrollbars(this.graph.container) &&
          mxClient.IS_FF &&
          mxClient.IS_WIN &&
          me.getState() == null &&
          mxEvent.isTouchEvent(me.getEvent()))
      );
    };

    // Shows hand cursor while panning
    var prevCursor = null;

    this.panningHandler.addListener(
      mxEvent.PAN_START,
      mxUtils.bind(this, function () {
        if (this.isEnabled()) {
          prevCursor = this.container.style.cursor;
          this.container.style.cursor = 'move';
        }
      })
    );

    this.panningHandler.addListener(
      mxEvent.PAN_END,
      mxUtils.bind(this, function () {
        if (this.isEnabled()) {
          this.container.style.cursor = prevCursor;
        }
      })
    );

    this.popupMenuHandler.autoExpand = true;

    this.popupMenuHandler.isSelectOnPopup = function (me) {
      return mxEvent.isMouseEvent(me.getEvent());
    };

    // Handles links if graph is read-only or cell is locked
    var click = this.click;
    this.click = function (me) {
      var locked = me.state == null && me.sourceState != null && this.isCellLocked(me.sourceState.cell);

      if ((!this.isEnabled() || locked) && !me.isConsumed()) {
        var cell = locked ? me.sourceState.cell : me.getCell();

        if (cell != null) {
          var link = this.getClickableLinkForCell(cell);

          if (link != null) {
            if (this.isCustomLink(link)) {
              this.customLinkClicked(link);
            } else {
              this.openLink(link);
            }
          }
        }

        if (this.isEnabled() && locked) {
          this.clearSelection();
        }
      } else {
        return click.apply(this, arguments);
      }
    };

    // Redirects tooltips for locked cells
    this.tooltipHandler.getStateForEvent = function (me) {
      return me.sourceState;
    };

    // Opens links in tooltips in new windows
    var tooltipHandlerShow = this.tooltipHandler.show;
    this.tooltipHandler.show = function () {
      tooltipHandlerShow.apply(this, arguments);

      if (this.div != null) {
        var links = this.div.getElementsByTagName('a');

        for (var i = 0; i < links.length; i++) {
          if (links[i].getAttribute('href') != null && links[i].getAttribute('target') == null) {
            links[i].setAttribute('target', '_blank');
          }
        }
      }
    };

    // Redirects tooltips for locked cells
    this.tooltipHandler.getStateForEvent = function (me) {
      return me.sourceState;
    };

    // Redirects cursor for locked cells
    var getCursorForMouseEvent = this.getCursorForMouseEvent;
    this.getCursorForMouseEvent = function (me) {
      var locked = me.state == null && me.sourceState != null && this.isCellLocked(me.sourceState.cell);

      return this.getCursorForCell(locked ? me.sourceState.cell : me.getCell());
    };

    // Shows pointer cursor for clickable cells with links
    // ie. if the graph is disabled and cells cannot be selected
    var getCursorForCell = this.getCursorForCell;
    this.getCursorForCell = function (cell) {
      if (!this.isEnabled() || this.isCellLocked(cell)) {
        var link = this.getClickableLinkForCell(cell);

        if (link != null) {
          return 'pointer';
        } else if (this.isCellLocked(cell)) {
          return 'default';
        }
      }

      return getCursorForCell.apply(this, arguments);
    };

    // Changes rubberband selection ignore locked cells
    this.selectRegion = function (rect, evt) {
      var cells = this.getCells(
        rect.x,
        rect.y,
        rect.width,
        rect.height,
        null,
        null,
        null,
        function (state) {
          return mxUtils.getValue(state.style, 'locked', '0') == '1';
        },
        true
      );

      this.selectCellsForEvent(cells, evt);

      return cells;
    };

    // Never removes cells from parents that are being moved
    var graphHandlerShouldRemoveCellsFromParent = this.graphHandler.shouldRemoveCellsFromParent;
    this.graphHandler.shouldRemoveCellsFromParent = function (parent, cells, evt) {
      if (this.graph.isCellSelected(parent)) {
        return false;
      }

      return graphHandlerShouldRemoveCellsFromParent.apply(this, arguments);
    };

    // Unlocks all cells
    this.isCellLocked = function (cell) {
      var pState = this.view.getState(cell);

      while (pState != null) {
        if (mxUtils.getValue(pState.style, 'locked', '0') == '1') {
          return true;
        }

        pState = this.view.getState(this.model.getParent(pState.cell));
      }

      return false;
    };

    var tapAndHoldSelection = null;

    // Uses this event to process mouseDown to check the selection state before it is changed
    this.addListener(
      mxEvent.FIRE_MOUSE_EVENT,
      mxUtils.bind(this, function (sender, evt) {
        if (evt.getProperty('eventName') == 'mouseDown') {
          var me = evt.getProperty('event');
          var state = me.getState();

          if (state != null && !this.isSelectionEmpty() && !this.isCellSelected(state.cell)) {
            tapAndHoldSelection = this.getSelectionCells();
          } else {
            tapAndHoldSelection = null;
          }
        }
      })
    );

    // Tap and hold on background starts rubberband for multiple selected
    // cells the cell associated with the event is deselected
    this.addListener(
      mxEvent.TAP_AND_HOLD,
      mxUtils.bind(this, function (sender, evt) {
        if (!mxEvent.isMultiTouchEvent(evt)) {
          var me = evt.getProperty('event');
          var cell = evt.getProperty('cell');

          if (cell == null) {
            var pt = mxUtils.convertPoint(this.container, mxEvent.getClientX(me), mxEvent.getClientY(me));
            rubberband.start(pt.x, pt.y);
          } else if (tapAndHoldSelection != null) {
            this.addSelectionCells(tapAndHoldSelection);
          } else if (this.getSelectionCount() > 1 && this.isCellSelected(cell)) {
            this.removeSelectionCell(cell);
          }

          // Blocks further processing of the event
          tapAndHoldSelection = null;
          evt.consume();
        }
      })
    );

    // On connect the target is selected and we clone the cell of the preview edge for insert
    this.connectionHandler.selectCells = function (edge, target) {
      this.graph.setSelectionCell(target || edge);
    };

    // Shows connection points only if cell not selected and parent table not handled
    this.connectionHandler.constraintHandler.isStateIgnored = function (state, source) {
      var graph = state.view.graph;

      return (
        source &&
        (graph.isCellSelected(state.cell) ||
          (graph.isTableRow(state.cell) && graph.selectionCellsHandler.isHandled(graph.model.getParent(state.cell))))
      );
    };

    // Updates constraint handler if the selection changes
    this.selectionModel.addListener(
      mxEvent.CHANGE,
      mxUtils.bind(this, function () {
        var ch = this.connectionHandler.constraintHandler;

        if (ch.currentFocus != null && ch.isStateIgnored(ch.currentFocus, true)) {
          ch.currentFocus = null;
          ch.constraints = null;
          ch.destroyIcons();
        }

        ch.destroyFocusHighlight();
      })
    );

    // Initializes touch interface
    if (Graph.touchStyle) {
      this.initTouch();
    }

    /**
     * Adds locking
     */
    var graphUpdateMouseEvent = this.updateMouseEvent;
    this.updateMouseEvent = function (me) {
      me = graphUpdateMouseEvent.apply(this, arguments);

      if (me.state != null && this.isCellLocked(me.getCell())) {
        me.state = null;
      }

      return me;
    };
  }

  //Create a unique offset object for each graph instance.
  this.currentTranslate = new mxPoint(0, 0);
};

/**
 * Specifies if the touch UI should be used (cannot detect touch in FF so always on for Windows/Linux)
 */
Graph.touchStyle =
  mxClient.IS_TOUCH ||
  (mxClient.IS_FF && mxClient.IS_WIN) ||
  navigator.maxTouchPoints > 0 ||
  navigator.msMaxTouchPoints > 0 ||
  window.urlParams == null ||
  urlParams['touch'] == '1';

/**
 * Shortcut for capability check.
 */
Graph.fileSupport =
  window.File != null &&
  window.FileReader != null &&
  window.FileList != null &&
  (window.urlParams == null || urlParams['filesupport'] != '0');

/**
 * Shortcut for capability check.
 */
Graph.translateDiagram = urlParams['translate-diagram'] == '1';

/**
 * Shortcut for capability check.
 */
Graph.diagramLanguage = urlParams['diagram-language'] != null ? urlParams['diagram-language'] : mxClient.language;

/**
 * Default size for line jumps.
 */
Graph.lineJumpsEnabled = true;

/**
 * Default size for line jumps.
 */
Graph.defaultJumpSize = 6;

/**
 * Minimum width for table columns.
 */
Graph.minTableColumnWidth = 20;

/**
 * Minimum height for table rows.
 */
Graph.minTableRowHeight = 20;

/**
 * Text for foreign object warning.
 */
Graph.foreignObjectWarningText = 'Viewer does not support full SVG 1.1';

/**
 * Link for foreign object warning.
 */
Graph.foreignObjectWarningLink = 'https://desk.draw.io/support/solutions/articles/16000042487';

/**
 * Minimum height for table rows.
 */
Graph.pasteStyles = [
  'rounded',
  'shadow',
  'dashed',
  'dashPattern',
  'fontFamily',
  'fontSource',
  'fontSize',
  'fontColor',
  'fontStyle',
  'align',
  'verticalAlign',
  'strokeColor',
  'strokeWidth',
  'fillColor',
  'gradientColor',
  'swimlaneFillColor',
  'textOpacity',
  'gradientDirection',
  'glass',
  'labelBackgroundColor',
  'labelBorderColor',
  'opacity',
  'spacing',
  'spacingTop',
  'spacingLeft',
  'spacingBottom',
  'spacingRight',
  'endFill',
  'endArrow',
  'endSize',
  'targetPerimeterSpacing',
  'startFill',
  'startArrow',
  'startSize',
  'sourcePerimeterSpacing',
  'arcSize',
  'comic',
  'sketch',
  'fillWeight',
  'hachureGap',
  'hachureAngle',
  'jiggle',
  'disableMultiStroke',
  'disableMultiStrokeFill',
  'fillStyle',
  'curveFitting',
  'simplification',
  'comicStyle',
];

/**
 * Helper function for creating SVG data URI.
 */
Graph.createSvgImage = function (w, h, data, coordWidth, coordHeight) {
  var tmp = unescape(
    encodeURIComponent(
      '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">' +
        '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="' +
        w +
        'px" height="' +
        h +
        'px" ' +
        (coordWidth != null && coordHeight != null ? 'viewBox="0 0 ' + coordWidth + ' ' + coordHeight + '" ' : '') +
        'version="1.1">' +
        data +
        '</svg>'
    )
  );

  return new mxImage('data:image/svg+xml;base64,' + (window.btoa ? btoa(tmp) : Base64.encode(tmp, true)), w, h);
};

/**
 * Removes all illegal control characters with ASCII code <32 except TAB, LF
 * and CR.
 */
Graph.zapGremlins = function (text) {
  var lastIndex = 0;
  var checked = [];

  for (var i = 0; i < text.length; i++) {
    var code = text.charCodeAt(i);

    // Removes all control chars except TAB, LF and CR
    if (!((code >= 32 || code == 9 || code == 10 || code == 13) && code != 0xffff && code != 0xfffe)) {
      checked.push(text.substring(lastIndex, i));
      lastIndex = i + 1;
    }
  }

  if (lastIndex > 0 && lastIndex < text.length) {
    checked.push(text.substring(lastIndex));
  }

  return checked.length == 0 ? text : checked.join('');
};

/**
 * Turns the given string into an array.
 */
Graph.stringToBytes = function (str) {
  var arr = new Array(str.length);

  for (var i = 0; i < str.length; i++) {
    arr[i] = str.charCodeAt(i);
  }

  return arr;
};

/**
 * Turns the given array into a string.
 */
Graph.bytesToString = function (arr) {
  var result = new Array(arr.length);

  for (var i = 0; i < arr.length; i++) {
    result[i] = String.fromCharCode(arr[i]);
  }

  return result.join('');
};

/**
 * Turns the given array into a string.
 */
Graph.base64EncodeUnicode = function (str) {
  return btoa(
    encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) {
      return String.fromCharCode(parseInt(p1, 16));
    })
  );
};

/**
 * Turns the given array into a string.
 */
Graph.base64DecodeUnicode = function (str) {
  return decodeURIComponent(
    Array.prototype.map
      .call(atob(str), function (c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
      })
      .join('')
  );
};

/**
 * Returns a base64 encoded version of the compressed outer XML of the given node.
 */
Graph.compressNode = function (node, checked) {
  var xml = mxUtils.getXml(node);

  return Graph.compress(checked ? xml : Graph.zapGremlins(xml));
};

/**
 * Returns a base64 encoded version of the compressed string.
 */
Graph.compress = function (data, deflate) {
  if (data == null || data.length == 0 || typeof pako === 'undefined') {
    return data;
  } else {
    var tmp = deflate
      ? pako.deflate(encodeURIComponent(data), { to: 'string' })
      : pako.deflateRaw(encodeURIComponent(data), { to: 'string' });

    return window.btoa ? btoa(tmp) : Base64.encode(tmp, true);
  }
};

/**
 * Returns a decompressed version of the base64 encoded string.
 */
Graph.decompress = function (data, inflate, checked) {
  if (data == null || data.length == 0 || typeof pako === 'undefined') {
    return data;
  } else {
    var tmp = window.atob ? atob(data) : Base64.decode(data, true);

    var inflated = decodeURIComponent(
      inflate ? pako.inflate(tmp, { to: 'string' }) : pako.inflateRaw(tmp, { to: 'string' })
    );

    return checked ? inflated : Graph.zapGremlins(inflated);
  }
};

/**
 * Removes formatting from pasted HTML.
 */
Graph.removePasteFormatting = function (elt) {
  while (elt != null) {
    if (elt.firstChild != null) {
      Graph.removePasteFormatting(elt.firstChild);
    }

    if (elt.nodeType == mxConstants.NODETYPE_ELEMENT && elt.style != null) {
      elt.style.whiteSpace = '';

      if (elt.style.color == '#000000') {
        elt.style.color = '';
      }
    }

    elt = elt.nextSibling;
  }
};

/**
 * Sanitizes the given HTML markup.
 */
Graph.sanitizeHtml = function (value, editing) {
  // Uses https://code.google.com/p/google-caja/wiki/JsHtmlSanitizer
  // NOTE: Original minimized sanitizer was modified to support
  // data URIs for images, mailto and special data:-links.
  // LATER: Add MathML to whitelisted tags
  function urlX(link) {
    if (link != null && link.toString().toLowerCase().substring(0, 11) !== 'javascript:') {
      return link;
    }

    return null;
  }
  function idX(id) {
    return id;
  }

  return html_sanitize(value, urlX, idX);
};

/**
 * Returns the CSS font family from the given computed style.
 */
Graph.stripQuotes = function (text) {
  if (text != null) {
    if (text.charAt(0) == "'") {
      text = text.substring(1);
    }

    if (text.charAt(text.length - 1) == "'") {
      text = text.substring(0, text.length - 1);
    }

    if (text.charAt(0) == '"') {
      text = text.substring(1);
    }

    if (text.charAt(text.length - 1) == '"') {
      text = text.substring(0, text.length - 1);
    }
  }

  return text;
};

/**
 * Returns true if the given string is a link.
 *
 * See https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
 */
Graph.isLink = function (text) {
  return text != null && Graph.linkPattern.test(text);
};

/**
 * Regular expression for links.
 */
Graph.linkPattern = new RegExp(
  '^(https?:\\/\\/)?' + // protocol
    '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
    '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
    '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
    '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
    '(\\#[-a-z\\d_]*)?$',
  'i'
); // fragment locator

/**
 * Graph inherits from mxGraph.
 */
mxUtils.extend(Graph, mxGraph);

/**
 * Allows all values in fit.
 */
Graph.prototype.minFitScale = null;

/**
 * Allows all values in fit.
 */
Graph.prototype.maxFitScale = null;

/**
 * Sets the policy for links. Possible values are "self" to replace any framesets,
 * "blank" to load the URL in <linkTarget> and "auto" (default).
 */
Graph.prototype.linkPolicy = urlParams['target'] == 'frame' ? 'blank' : urlParams['target'] || 'auto';

/**
 * Target for links that open in a new window. Default is _blank.
 */
Graph.prototype.linkTarget = urlParams['target'] == 'frame' ? '_self' : '_blank';

/**
 * Value to the rel attribute of links. Default is 'nofollow noopener noreferrer'.
 * NOTE: There are security implications when this is changed and if noopener is removed,
 * then <openLink> must be overridden to allow for the opener to be set by default.
 */
Graph.prototype.linkRelation = 'nofollow noopener noreferrer';

/**
 * Scrollbars are enabled on non-touch devices (not including Firefox because touch events
 * cannot be detected in Firefox, see above).
 */
Graph.prototype.defaultScrollbars = !mxClient.IS_IOS;

/**
 * Specifies if the page should be visible for new files. Default is true.
 */
Graph.prototype.defaultPageVisible = true;

/**
 * Specifies if the app should run in chromeless mode. Default is false.
 * This default is only used if the contructor argument is null.
 */
Graph.prototype.lightbox = false;

/**
 *
 */
Graph.prototype.defaultPageBackgroundColor = '#ffffff';

/**
 *
 */
Graph.prototype.defaultPageBorderColor = '#ffffff';

/**
 * Specifies the size of the size for "tiles" to be used for a graph with
 * scrollbars but no visible background page. A good value is large
 * enough to reduce the number of repaints that is caused for auto-
 * translation, which depends on this value, and small enough to give
 * a small empty buffer around the graph. Default is 400x400.
 */
Graph.prototype.scrollTileSize = new mxRectangle(0, 0, 400, 400);

/**
 * Overrides the background color and paints a transparent background.
 */
Graph.prototype.transparentBackground = true;

/**
 * Sets global constants.
 */
Graph.prototype.selectParentAfterDelete = false;

/**
 * Sets the default target for all links in cells.
 */
Graph.prototype.defaultEdgeLength = 80;

/**
 * Disables move of bends/segments without selecting.
 */
Graph.prototype.edgeMode = false;

/**
 * Allows all values in fit.
 */
Graph.prototype.connectionArrowsEnabled = true;

/**
 * Specifies the regular expression for matching placeholders.
 */
Graph.prototype.placeholderPattern = new RegExp('%(date{.*}|[^%^{^}]+)%', 'g');

/**
 * Specifies the regular expression for matching placeholders.
 */
Graph.prototype.absoluteUrlPattern = new RegExp('^(?:[a-z]+:)?//', 'i');

/**
 * Specifies the default name for the theme. Default is 'default'.
 */
Graph.prototype.defaultThemeName = 'default';

/**
 * Specifies the default name for the theme. Default is 'default'.
 */
Graph.prototype.defaultThemes = {};

/**
 * Base URL for relative links.
 */
Graph.prototype.baseUrl =
  urlParams['base'] != null
    ? decodeURIComponent(urlParams['base'])
    : (window != window.top ? document.referrer : document.location.toString()).split('#')[0];

/**
 * Specifies if the label should be edited after an insert.
 */
Graph.prototype.editAfterInsert = false;

/**
 * Defines the built-in properties to be ignored in tooltips.
 */
Graph.prototype.builtInProperties = ['label', 'tooltip', 'placeholders', 'placeholder'];

/**
 * Defines if the graph is part of an EditorUi. If this is false the graph can
 * be used in an EditorUi instance but will not have a UI added, functions
 * overridden or event handlers added.
 */
Graph.prototype.standalone = false;

/**
 * Installs child layout styles.
 */
Graph.prototype.init = function (container) {
  mxGraph.prototype.init.apply(this, arguments);

  // Intercepts links with no target attribute and opens in new window
  this.cellRenderer.initializeLabel = function (state, shape) {
    mxCellRenderer.prototype.initializeLabel.apply(this, arguments);

    // Checks tolerance for clicks on links
    var tol = state.view.graph.tolerance;
    var handleClick = true;
    var first = null;

    var down = mxUtils.bind(this, function (evt) {
      handleClick = true;
      first = new mxPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt));
    });

    var move = mxUtils.bind(this, function (evt) {
      handleClick =
        handleClick &&
        first != null &&
        Math.abs(first.x - mxEvent.getClientX(evt)) < tol &&
        Math.abs(first.y - mxEvent.getClientY(evt)) < tol;
    });

    var up = mxUtils.bind(this, function (evt) {
      if (handleClick) {
        var elt = mxEvent.getSource(evt);

        while (elt != null && elt != shape.node) {
          if (elt.nodeName.toLowerCase() == 'a') {
            state.view.graph.labelLinkClicked(state, elt, evt);
            break;
          }

          elt = elt.parentNode;
        }
      }
    });

    mxEvent.addGestureListeners(shape.node, down, move, up);
    mxEvent.addListener(shape.node, 'click', function (evt) {
      mxEvent.consume(evt);
    });
  };

  this.initLayoutManager();
};

/**
 * Implements zoom and offset via CSS transforms. This is currently only used
 * in read-only as there are fewer issues with the mxCellState not being scaled
 * and translated.
 *
 * KNOWN ISSUES TO FIX:
 * - Apply CSS transforms to HTML labels in IE11
 */
(function () {
  /**
   * Uses CSS transforms for scale and translate.
   */
  Graph.prototype.useCssTransforms = false;

  /**
   * Contains the scale.
   */
  Graph.prototype.currentScale = 1;

  /**
   * Contains the offset.
   */
  Graph.prototype.currentTranslate = new mxPoint(0, 0);

  /**
   *
   */
  Graph.prototype.getVerticesAndEdges = function (vertices, edges) {
    vertices = vertices != null ? vertices : true;
    edges = edges != null ? edges : true;
    var model = this.model;

    return model.filterDescendants(function (cell) {
      return (vertices && model.isVertex(cell)) || (edges && model.isEdge(cell));
    }, model.getRoot());
  };

  /**
   * Returns the cell for editing the given cell.
   */
  Graph.prototype.getStartEditingCell = function (cell, trigger) {
    // Redirect editing for tables
    var style = this.getCellStyle(cell);
    var size = parseInt(mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, 0));

    if (
      this.isTable(cell) &&
      (!this.isSwimlane(cell) || size == 0) &&
      this.getLabel(cell) == '' &&
      this.model.getChildCount(cell) > 0
    ) {
      cell = this.model.getChildAt(cell, 0);

      style = this.getCellStyle(cell);
      size = parseInt(mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, 0));
    }

    // Redirect editing for table rows
    if (
      this.isTableRow(cell) &&
      (!this.isSwimlane(cell) || size == 0) &&
      this.getLabel(cell) == '' &&
      this.model.getChildCount(cell) > 0
    ) {
      for (var i = 0; i < this.model.getChildCount(cell); i++) {
        var temp = this.model.getChildAt(cell, i);

        if (this.isCellEditable(temp)) {
          cell = temp;
          break;
        }
      }
    }

    return cell;
  };

  /**
   * Returns true if fast zoom preview should be used.
   */
  Graph.prototype.copyStyle = function (cell) {
    var style = null;

    if (cell != null) {
      style = mxUtils.clone(this.getCurrentCellStyle(cell));

      // Handles special case for value "none"
      var cellStyle = this.model.getStyle(cell);
      var tokens = cellStyle != null ? cellStyle.split(';') : [];

      for (var j = 0; j < tokens.length; j++) {
        var tmp = tokens[j];
        var pos = tmp.indexOf('=');

        if (pos >= 0) {
          var key = tmp.substring(0, pos);
          var value = tmp.substring(pos + 1);

          if (style[key] == null && value == mxConstants.NONE) {
            style[key] = mxConstants.NONE;
          }
        }
      }
    }

    return style;
  };

  /**
   * Returns true if fast zoom preview should be used.
   */
  Graph.prototype.pasteStyle = function (style, cells, keys) {
    keys = keys != null ? keys : Graph.pasteStyles;

    this.model.beginUpdate();
    try {
      for (var i = 0; i < cells.length; i++) {
        var temp = this.getCurrentCellStyle(cells[i]);

        for (var j = 0; j < keys.length; j++) {
          var current = temp[keys[j]];
          var value = style[keys[j]];

          if (current != value && (current != null || value != mxConstants.NONE)) {
            this.setCellStyles(keys[j], value, [cells[i]]);
          }
        }
      }
    } finally {
      this.model.endUpdate();
    }
  };

  /**
   * Returns true if fast zoom preview should be used.
   */
  Graph.prototype.isFastZoomEnabled = function () {
    return (
      urlParams['zoom'] != 'nocss' &&
      !mxClient.NO_FO &&
      !mxClient.IS_EDGE &&
      !this.useCssTransforms &&
      this.isCssTransformsSupported()
    );
  };

  /**
   * Only foreignObject supported for now (no IE11). Safari disabled as it ignores
   * overflow visible on foreignObject in negative space (lightbox and viewer).
   * Check the following test case on page 1 before enabling this in production:
   * https://devhost.jgraph.com/git/drawio/etc/embed/sf-math-fo-clipping.html?dev=1
   */
  Graph.prototype.isCssTransformsSupported = function () {
    return this.dialect == mxConstants.DIALECT_SVG && !mxClient.NO_FO && (!this.lightbox || !mxClient.IS_SF);
  };

  /**
   * Function: getCellAt
   *
   * Needs to modify original method for recursive call.
   */
  Graph.prototype.getCellAt = function (x, y, parent, vertices, edges, ignoreFn) {
    if (this.useCssTransforms) {
      x = x / this.currentScale - this.currentTranslate.x;
      y = y / this.currentScale - this.currentTranslate.y;
    }

    return this.getScaledCellAt.apply(this, arguments);
  };

  /**
   * Function: getScaledCellAt
   *
   * Overridden for recursion.
   */
  Graph.prototype.getScaledCellAt = function (x, y, parent, vertices, edges, ignoreFn) {
    vertices = vertices != null ? vertices : true;
    edges = edges != null ? edges : true;

    if (parent == null) {
      parent = this.getCurrentRoot();

      if (parent == null) {
        parent = this.getModel().getRoot();
      }
    }

    if (parent != null) {
      var childCount = this.model.getChildCount(parent);

      for (var i = childCount - 1; i >= 0; i--) {
        var cell = this.model.getChildAt(parent, i);
        var result = this.getScaledCellAt(x, y, cell, vertices, edges, ignoreFn);

        if (result != null) {
          return result;
        } else if (
          this.isCellVisible(cell) &&
          ((edges && this.model.isEdge(cell)) || (vertices && this.model.isVertex(cell)))
        ) {
          var state = this.view.getState(cell);

          if (state != null && (ignoreFn == null || !ignoreFn(state, x, y)) && this.intersects(state, x, y)) {
            return cell;
          }
        }
      }
    }

    return null;
  };

  /**
   * Returns if the child cells of the given vertex cell state should be resized.
   */
  Graph.prototype.isRecursiveVertexResize = function (state) {
    return (
      !this.isSwimlane(state.cell) &&
      this.model.getChildCount(state.cell) > 0 &&
      !this.isCellCollapsed(state.cell) &&
      mxUtils.getValue(state.style, 'recursiveResize', '1') == '1' &&
      mxUtils.getValue(state.style, 'childLayout', null) == null
    );
  };

  /**
   * Returns the first parent that is not a part.
   */
  Graph.prototype.isPart = function (cell) {
    return (
      mxUtils.getValue(this.getCurrentCellStyle(cell), 'part', '0') == '1' ||
      this.isTableCell(cell) ||
      this.isTableRow(cell)
    );
  };

  /**
   * Returns the first parent that is not a part.
   */
  Graph.prototype.getCompositeParent = function (cell) {
    while (this.isPart(cell)) {
      var temp = this.model.getParent(cell);

      if (!this.model.isVertex(temp)) {
        break;
      }

      cell = temp;
    }

    return cell;
  };

  /**
   * Function: repaint
   *
   * Updates the highlight after a change of the model or view.
   */
  mxCellHighlight.prototype.getStrokeWidth = function (state) {
    var s = this.strokeWidth;

    if (this.graph.useCssTransforms) {
      s /= this.graph.currentScale;
    }

    return s;
  };

  /**
   * Function: getGraphBounds
   *
   * Overrides getGraphBounds to use bounding box from SVG.
   */
  mxGraphView.prototype.getGraphBounds = function () {
    var b = this.graphBounds;

    if (this.graph.useCssTransforms) {
      var t = this.graph.currentTranslate;
      var s = this.graph.currentScale;

      b = new mxRectangle((b.x + t.x) * s, (b.y + t.y) * s, b.width * s, b.height * s);
    }

    return b;
  };

  /**
   * Overrides to bypass full cell tree validation.
   * TODO: Check if this improves performance
   */
  mxGraphView.prototype.viewStateChanged = function () {
    if (this.graph.useCssTransforms) {
      this.validate();
      this.graph.sizeDidChange();
    } else {
      this.revalidate();
      this.graph.sizeDidChange();
    }
  };

  /**
   * Overrides validate to normalize validation view state and pass
   * current state to CSS transform.
   */
  var graphViewValidate = mxGraphView.prototype.validate;
  mxGraphView.prototype.validate = function (cell) {
    if (this.graph.useCssTransforms) {
      this.graph.currentScale = this.scale;
      this.graph.currentTranslate.x = this.translate.x;
      this.graph.currentTranslate.y = this.translate.y;

      this.scale = 1;
      this.translate.x = 0;
      this.translate.y = 0;
    }

    graphViewValidate.apply(this, arguments);

    if (this.graph.useCssTransforms) {
      this.graph.updateCssTransform();

      this.scale = this.graph.currentScale;
      this.translate.x = this.graph.currentTranslate.x;
      this.translate.y = this.graph.currentTranslate.y;
    }
  };

  /**
   * Overrides function to exclude table cells and rows from groups.
   */
  var graphGetCellsForGroup = mxGraph.prototype.getCellsForGroup;
  Graph.prototype.getCellsForGroup = function (cells) {
    cells = graphGetCellsForGroup.apply(this, arguments);
    var result = [];

    // Filters selection cells with the same parent
    for (var i = 0; i < cells.length; i++) {
      if (!this.isTableRow(cells[i]) && !this.isTableCell(cells[i])) {
        result.push(cells[i]);
      }
    }

    return result;
  };

  /**
   * Overrides function to exclude tables, rows and cells from ungrouping.
   */
  var graphGetCellsForUngroup = mxGraph.prototype.getCellsForUngroup;
  Graph.prototype.getCellsForUngroup = function (cells) {
    cells = graphGetCellsForUngroup.apply(this, arguments);
    var result = [];

    // Filters selection cells with the same parent
    for (var i = 0; i < cells.length; i++) {
      if (!this.isTable(cells[i]) && !this.isTableRow(cells[i]) && !this.isTableCell(cells[i])) {
        result.push(cells[i]);
      }
    }

    return result;
  };

  /**
   * Function: updateCssTransform
   *
   * Zooms out of the graph by <zoomFactor>.
   */
  Graph.prototype.updateCssTransform = function () {
    var temp = this.view.getDrawPane();

    if (temp != null) {
      var g = temp.parentNode;

      if (!this.useCssTransforms) {
        g.removeAttribute('transformOrigin');
        g.removeAttribute('transform');
      } else {
        var prev = g.getAttribute('transform');
        g.setAttribute('transformOrigin', '0 0');
        var s = Math.round(this.currentScale * 100) / 100;
        var dx = Math.round(this.currentTranslate.x * 100) / 100;
        var dy = Math.round(this.currentTranslate.y * 100) / 100;
        g.setAttribute('transform', 'scale(' + s + ',' + s + ')' + 'translate(' + dx + ',' + dy + ')');

        // Applies workarounds only if translate has changed
        if (prev != g.getAttribute('transform')) {
          try {
            // Applies transform to labels outside of the SVG DOM
            // Excluded via isCssTransformsSupported
            //					if (mxClient.NO_FO)
            //					{
            //						var transform = 'scale(' + this.currentScale + ')' + 'translate(' +
            //							this.currentTranslate.x + 'px,' + this.currentTranslate.y + 'px)';
            //
            //						this.view.states.visit(mxUtils.bind(this, function(cell, state)
            //						{
            //							if (state.text != null && state.text.node != null)
            //							{
            //								// Stores initial CSS transform that is used for the label alignment
            //								if (state.text.originalTransform == null)
            //								{
            //									state.text.originalTransform = state.text.node.style.transform;
            //								}
            //
            //								state.text.node.style.transform = transform + state.text.originalTransform;
            //							}
            //						}));
            //					}
            // Workaround for https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4320441/
            if (mxClient.IS_EDGE) {
              // Recommended workaround is to do this on all
              // foreignObjects, but this seems to be faster
              var val = g.style.display;
              g.style.display = 'none';
              g.getBBox();
              g.style.display = val;
            }
          } catch (e) {
            // ignore
          }
        }
      }
    }
  };

  var graphViewValidateBackgroundPage = mxGraphView.prototype.validateBackgroundPage;
  mxGraphView.prototype.validateBackgroundPage = function () {
    var useCssTranforms = this.graph.useCssTransforms,
      scale = this.scale,
      translate = this.translate;

    if (useCssTranforms) {
      this.scale = this.graph.currentScale;
      this.translate = this.graph.currentTranslate;
    }

    graphViewValidateBackgroundPage.apply(this, arguments);

    if (useCssTranforms) {
      this.scale = scale;
      this.translate = translate;
    }
  };

  var graphUpdatePageBreaks = mxGraph.prototype.updatePageBreaks;
  mxGraph.prototype.updatePageBreaks = function (visible, width, height) {
    var useCssTranforms = this.useCssTransforms,
      scale = this.view.scale,
      translate = this.view.translate;

    if (useCssTranforms) {
      this.view.scale = 1;
      this.view.translate = new mxPoint(0, 0);
      this.useCssTransforms = false;
    }

    graphUpdatePageBreaks.apply(this, arguments);

    if (useCssTranforms) {
      this.view.scale = scale;
      this.view.translate = translate;
      this.useCssTransforms = true;
    }
  };
})();

/**
 * Sets the XML node for the current diagram.
 */
Graph.prototype.isLightboxView = function () {
  return this.lightbox;
};

/**
 * Sets the XML node for the current diagram.
 */
Graph.prototype.isViewer = function () {
  return false;
};

/**
 * Installs automatic layout via styles
 */
Graph.prototype.labelLinkClicked = function (state, elt, evt) {
  var href = elt.getAttribute('href');

  if (
    href != null &&
    !this.isCustomLink(href) &&
    ((mxEvent.isLeftMouseButton(evt) && !mxEvent.isPopupTrigger(evt)) || mxEvent.isTouchEvent(evt))
  ) {
    if (!this.isEnabled() || this.isCellLocked(state.cell)) {
      var target = this.isBlankLink(href) ? this.linkTarget : '_top';
      this.openLink(this.getAbsoluteUrl(href), target);
    }

    mxEvent.consume(evt);
  }
};

/**
 * Returns the size of the page format scaled with the page size.
 */
Graph.prototype.openLink = function (href, target, allowOpener) {
  var result = window;

  try {
    // Workaround for blocking in same iframe
    if (target == '_self' && window != window.top) {
      window.location.href = href;
    } else {
      // Avoids page reload for anchors (workaround for IE but used everywhere)
      if (
        href.substring(0, this.baseUrl.length) == this.baseUrl &&
        href.charAt(this.baseUrl.length) == '#' &&
        target == '_top' &&
        window == window.top
      ) {
        var hash = href.split('#')[1];

        // Forces navigation if on same hash
        if (window.location.hash == '#' + hash) {
          window.location.hash = '';
        }

        window.location.hash = hash;
      } else {
        result = window.open(href, target != null ? target : '_blank');

        if (result != null && !allowOpener) {
          result.opener = null;
        }
      }
    }
  } catch (e) {
    // ignores permission denied
  }

  return result;
};

/**
 * Adds support for page links.
 */
Graph.prototype.getLinkTitle = function (href) {
  return href.substring(href.lastIndexOf('/') + 1);
};

/**
 * Adds support for page links.
 */
Graph.prototype.isCustomLink = function (href) {
  return href.substring(0, 5) == 'data:';
};

/**
 * Adds support for page links.
 */
Graph.prototype.customLinkClicked = function (link) {
  return false;
};

/**
 * Returns true if the given href references an external protocol that
 * should never open in a new window. Default returns true for mailto.
 */
Graph.prototype.isExternalProtocol = function (href) {
  return href.substring(0, 7) === 'mailto:';
};

/**
 * Hook for links to open in same window. Default returns true for anchors,
 * links to same domain or if target == 'self' in the config.
 */
Graph.prototype.isBlankLink = function (href) {
  return (
    !this.isExternalProtocol(href) &&
    (this.linkPolicy === 'blank' ||
      (this.linkPolicy !== 'self' &&
        !this.isRelativeUrl(href) &&
        href.substring(0, this.domainUrl.length) !== this.domainUrl))
  );
};

/**
 *
 */
Graph.prototype.isRelativeUrl = function (url) {
  return (
    url != null &&
    !this.absoluteUrlPattern.test(url) &&
    url.substring(0, 5) !== 'data:' &&
    !this.isExternalProtocol(url)
  );
};

/**
 *
 */
Graph.prototype.getAbsoluteUrl = function (url) {
  if (url != null && this.isRelativeUrl(url)) {
    if (url.charAt(0) == '#') {
      url = this.baseUrl + url;
    } else if (url.charAt(0) == '/') {
      url = this.domainUrl + url;
    } else {
      url = this.domainPathUrl + url;
    }
  }

  return url;
};

/**
 * Installs automatic layout via styles
 */
Graph.prototype.initLayoutManager = function () {
  this.layoutManager = new mxLayoutManager(this);

  this.layoutManager.hasLayout = function (cell, eventName) {
    return this.graph.getCellStyle(cell)['childLayout'] != null;
  };

  this.layoutManager.getLayout = function (cell, eventName) {
    var parent = this.graph.model.getParent(cell);

    // Executes layouts from top to bottom except for nested layouts where
    // child layouts are executed before and after the parent layout runs
    // in case the layout changes the size of the child cell
    if (eventName != mxEvent.BEGIN_UPDATE || this.hasLayout(parent, eventName)) {
      var style = this.graph.getCellStyle(cell);

      if (style['childLayout'] == 'stackLayout') {
        var stackLayout = new mxStackLayout(this.graph, true);
        stackLayout.resizeParentMax = mxUtils.getValue(style, 'resizeParentMax', '1') == '1';
        stackLayout.horizontal = mxUtils.getValue(style, 'horizontalStack', '1') == '1';
        stackLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1';
        stackLayout.resizeLast = mxUtils.getValue(style, 'resizeLast', '0') == '1';
        stackLayout.spacing = style['stackSpacing'] || stackLayout.spacing;
        stackLayout.border = style['stackBorder'] || stackLayout.border;
        stackLayout.marginLeft = style['marginLeft'] || 0;
        stackLayout.marginRight = style['marginRight'] || 0;
        stackLayout.marginTop = style['marginTop'] || 0;
        stackLayout.marginBottom = style['marginBottom'] || 0;
        stackLayout.allowGaps = style['allowGaps'] || 0;
        stackLayout.fill = true;

        if (stackLayout.allowGaps) {
          stackLayout.gridSize = parseFloat(mxUtils.getValue(style, 'stackUnitSize', 20));
        }

        return stackLayout;
      } else if (style['childLayout'] == 'treeLayout') {
        var treeLayout = new mxCompactTreeLayout(this.graph);
        treeLayout.horizontal = mxUtils.getValue(style, 'horizontalTree', '1') == '1';
        treeLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1';
        treeLayout.groupPadding = mxUtils.getValue(style, 'parentPadding', 20);
        treeLayout.levelDistance = mxUtils.getValue(style, 'treeLevelDistance', 30);
        treeLayout.maintainParentLocation = true;
        treeLayout.edgeRouting = false;
        treeLayout.resetEdges = false;

        return treeLayout;
      } else if (style['childLayout'] == 'flowLayout') {
        var flowLayout = new mxHierarchicalLayout(
          this.graph,
          mxUtils.getValue(style, 'flowOrientation', mxConstants.DIRECTION_EAST)
        );
        flowLayout.resizeParent = mxUtils.getValue(style, 'resizeParent', '1') == '1';
        flowLayout.parentBorder = mxUtils.getValue(style, 'parentPadding', 20);
        flowLayout.maintainParentLocation = true;

        // Special undocumented styles for changing the hierarchical
        flowLayout.intraCellSpacing = mxUtils.getValue(
          style,
          'intraCellSpacing',
          mxHierarchicalLayout.prototype.intraCellSpacing
        );
        flowLayout.interRankCellSpacing = mxUtils.getValue(
          style,
          'interRankCellSpacing',
          mxHierarchicalLayout.prototype.interRankCellSpacing
        );
        flowLayout.interHierarchySpacing = mxUtils.getValue(
          style,
          'interHierarchySpacing',
          mxHierarchicalLayout.prototype.interHierarchySpacing
        );
        flowLayout.parallelEdgeSpacing = mxUtils.getValue(
          style,
          'parallelEdgeSpacing',
          mxHierarchicalLayout.prototype.parallelEdgeSpacing
        );

        return flowLayout;
      } else if (style['childLayout'] == 'circleLayout') {
        return new mxCircleLayout(this.graph);
      } else if (style['childLayout'] == 'organicLayout') {
        return new mxFastOrganicLayout(this.graph);
      } else if (style['childLayout'] == 'tableLayout') {
        return new TableLayout(this.graph);
      }
    }

    return null;
  };
};

/**
 * Returns the size of the page format scaled with the page size.
 */
Graph.prototype.getPageSize = function () {
  return this.pageVisible
    ? new mxRectangle(0, 0, this.pageFormat.width * this.pageScale, this.pageFormat.height * this.pageScale)
    : this.scrollTileSize;
};

/**
 * Returns a rectangle describing the position and count of the
 * background pages, where x and y are the position of the top,
 * left page and width and height are the vertical and horizontal
 * page count.
 */
Graph.prototype.getPageLayout = function () {
  var size = this.getPageSize();
  var bounds = this.getGraphBounds();

  if (bounds.width == 0 || bounds.height == 0) {
    return new mxRectangle(0, 0, 1, 1);
  } else {
    var x0 = Math.floor(Math.ceil(bounds.x / this.view.scale - this.view.translate.x) / size.width);
    var y0 = Math.floor(Math.ceil(bounds.y / this.view.scale - this.view.translate.y) / size.height);
    var w0 =
      Math.ceil((Math.floor((bounds.x + bounds.width) / this.view.scale) - this.view.translate.x) / size.width) - x0;
    var h0 =
      Math.ceil((Math.floor((bounds.y + bounds.height) / this.view.scale) - this.view.translate.y) / size.height) - y0;

    return new mxRectangle(x0, y0, w0, h0);
  }
};

/**
 * Sanitizes the given HTML markup.
 */
Graph.prototype.sanitizeHtml = function (value, editing) {
  return Graph.sanitizeHtml(value, editing);
};

/**
 * Revalidates all cells with placeholders in the current graph model.
 */
Graph.prototype.updatePlaceholders = function () {
  var model = this.model;
  var validate = false;

  for (var key in this.model.cells) {
    var cell = this.model.cells[key];

    if (this.isReplacePlaceholders(cell)) {
      this.view.invalidate(cell, false, false);
      validate = true;
    }
  }

  if (validate) {
    this.view.validate();
  }
};

/**
 * Adds support for placeholders in labels.
 */
Graph.prototype.isReplacePlaceholders = function (cell) {
  return cell.value != null && typeof cell.value == 'object' && cell.value.getAttribute('placeholders') == '1';
};

/**
 * Returns true if the given mouse wheel event should be used for zooming. This
 * is invoked if no dialogs are showing and returns true with Alt or Control
 * (or cmd in macOS only) is pressed.
 */
Graph.prototype.isZoomWheelEvent = function (evt) {
  return mxEvent.isAltDown(evt) || (mxEvent.isMetaDown(evt) && mxClient.IS_MAC) || mxEvent.isControlDown(evt);
};

/**
 * Returns true if the given scroll wheel event should be used for scrolling.
 */
Graph.prototype.isScrollWheelEvent = function (evt) {
  return !this.isZoomWheelEvent(evt);
};

/**
 * Adds Alt+click to select cells behind cells (Shift+Click on Chrome OS).
 */
Graph.prototype.isTransparentClickEvent = function (evt) {
  return mxEvent.isAltDown(evt) || (mxClient.IS_CHROMEOS && mxEvent.isShiftDown(evt));
};

/**
 * Adds ctrl+shift+connect to disable connections.
 */
Graph.prototype.isIgnoreTerminalEvent = function (evt) {
  return mxEvent.isShiftDown(evt) && mxEvent.isControlDown(evt);
};

/**
 * Adds support for placeholders in labels.
 */
Graph.prototype.isSplitTarget = function (target, cells, evt) {
  return (
    !this.model.isEdge(cells[0]) &&
    !mxEvent.isAltDown(evt) &&
    !mxEvent.isShiftDown(evt) &&
    mxGraph.prototype.isSplitTarget.apply(this, arguments)
  );
};

/**
 * Adds support for placeholders in labels.
 */
Graph.prototype.getLabel = function (cell) {
  var result = mxGraph.prototype.getLabel.apply(this, arguments);

  if (result != null && this.isReplacePlaceholders(cell) && cell.getAttribute('placeholder') == null) {
    result = this.replacePlaceholders(cell, result);
  }

  return result;
};

/**
 * Adds labelMovable style.
 */
Graph.prototype.isLabelMovable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return (
    !this.isCellLocked(cell) &&
    ((this.model.isEdge(cell) && this.edgeLabelsMovable) ||
      (this.model.isVertex(cell) && (this.vertexLabelsMovable || mxUtils.getValue(style, 'labelMovable', '0') == '1')))
  );
};

/**
 * Adds event if grid size is changed.
 */
Graph.prototype.setGridSize = function (value) {
  this.gridSize = value;
  this.fireEvent(new mxEventObject('gridSizeChanged'));
};

/**
 * Adds event if default parent is changed.
 */
Graph.prototype.setDefaultParent = function (cell) {
  this.defaultParent = cell;
  this.fireEvent(new mxEventObject('defaultParentChanged'));
};

/**
 * Function: getClickableLinkForCell
 *
 * Returns the first non-null link for the cell or its ancestors.
 *
 * Parameters:
 *
 * cell - <mxCell> whose link should be returned.
 */
Graph.prototype.getClickableLinkForCell = function (cell) {
  do {
    var link = this.getLinkForCell(cell);

    if (link != null) {
      return link;
    }

    cell = this.model.getParent(cell);
  } while (cell != null);

  return null;
};

/**
 * Private helper method.
 */
Graph.prototype.getGlobalVariable = function (name) {
  var val = null;

  if (name == 'date') {
    val = new Date().toLocaleDateString();
  } else if (name == 'time') {
    val = new Date().toLocaleTimeString();
  } else if (name == 'timestamp') {
    val = new Date().toLocaleString();
  } else if (name.substring(0, 5) == 'date{') {
    var fmt = name.substring(5, name.length - 1);
    val = this.formatDate(new Date(), fmt);
  }

  return val;
};

/**
 * Formats a date, see http://blog.stevenlevithan.com/archives/date-time-format
 */
Graph.prototype.formatDate = function (date, mask, utc) {
  // LATER: Cache regexs
  if (this.dateFormatCache == null) {
    this.dateFormatCache = {
      i18n: {
        dayNames: [
          'Sun',
          'Mon',
          'Tue',
          'Wed',
          'Thu',
          'Fri',
          'Sat',
          'Sunday',
          'Monday',
          'Tuesday',
          'Wednesday',
          'Thursday',
          'Friday',
          'Saturday',
        ],
        monthNames: [
          'Jan',
          'Feb',
          'Mar',
          'Apr',
          'May',
          'Jun',
          'Jul',
          'Aug',
          'Sep',
          'Oct',
          'Nov',
          'Dec',
          'January',
          'February',
          'March',
          'April',
          'May',
          'June',
          'July',
          'August',
          'September',
          'October',
          'November',
          'December',
        ],
      },

      masks: {
        default: 'ddd mmm dd yyyy HH:MM:ss',
        shortDate: 'm/d/yy',
        mediumDate: 'mmm d, yyyy',
        longDate: 'mmmm d, yyyy',
        fullDate: 'dddd, mmmm d, yyyy',
        shortTime: 'h:MM TT',
        mediumTime: 'h:MM:ss TT',
        longTime: 'h:MM:ss TT Z',
        isoDate: 'yyyy-mm-dd',
        isoTime: 'HH:MM:ss',
        isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
        isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'",
      },
    };
  }

  var dF = this.dateFormatCache;
  var token = /d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
    timezone =
      /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
    timezoneClip = /[^-+\dA-Z]/g,
    pad = function (val, len) {
      val = String(val);
      len = len || 2;
      while (val.length < len) val = '0' + val;
      return val;
    };

  // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
  if (arguments.length == 1 && Object.prototype.toString.call(date) == '[object String]' && !/\d/.test(date)) {
    mask = date;
    date = undefined;
  }

  // Passing date through Date applies Date.parse, if necessary
  date = date ? new Date(date) : new Date();
  if (isNaN(date)) throw SyntaxError('invalid date');

  mask = String(dF.masks[mask] || mask || dF.masks['default']);

  // Allow setting the utc argument via the mask
  if (mask.slice(0, 4) == 'UTC:') {
    mask = mask.slice(4);
    utc = true;
  }

  var _ = utc ? 'getUTC' : 'get',
    d = date[_ + 'Date'](),
    D = date[_ + 'Day'](),
    m = date[_ + 'Month'](),
    y = date[_ + 'FullYear'](),
    H = date[_ + 'Hours'](),
    M = date[_ + 'Minutes'](),
    s = date[_ + 'Seconds'](),
    L = date[_ + 'Milliseconds'](),
    o = utc ? 0 : date.getTimezoneOffset(),
    flags = {
      d: d,
      dd: pad(d),
      ddd: dF.i18n.dayNames[D],
      dddd: dF.i18n.dayNames[D + 7],
      m: m + 1,
      mm: pad(m + 1),
      mmm: dF.i18n.monthNames[m],
      mmmm: dF.i18n.monthNames[m + 12],
      yy: String(y).slice(2),
      yyyy: y,
      h: H % 12 || 12,
      hh: pad(H % 12 || 12),
      H: H,
      HH: pad(H),
      M: M,
      MM: pad(M),
      s: s,
      ss: pad(s),
      l: pad(L, 3),
      L: pad(L > 99 ? Math.round(L / 10) : L),
      t: H < 12 ? 'a' : 'p',
      tt: H < 12 ? 'am' : 'pm',
      T: H < 12 ? 'A' : 'P',
      TT: H < 12 ? 'AM' : 'PM',
      Z: utc ? 'UTC' : (String(date).match(timezone) || ['']).pop().replace(timezoneClip, ''),
      o: (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + (Math.abs(o) % 60), 4),
      S: ['th', 'st', 'nd', 'rd'][d % 10 > 3 ? 0 : (((d % 100) - (d % 10) != 10) * d) % 10],
    };

  return mask.replace(token, function ($0) {
    return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
  });
};

/**
 *
 */
Graph.prototype.createLayersDialog = function () {
  var div = document.createElement('div');
  div.style.position = 'absolute';

  var model = this.getModel();
  var childCount = model.getChildCount(model.root);

  for (var i = 0; i < childCount; i++) {
    mxUtils.bind(this, function (layer) {
      var span = document.createElement('div');
      span.style.overflow = 'hidden';
      span.style.textOverflow = 'ellipsis';
      span.style.padding = '2px';
      span.style.whiteSpace = 'nowrap';

      var cb = document.createElement('input');
      cb.style.display = 'inline-block';
      cb.setAttribute('type', 'checkbox');

      if (model.isVisible(layer)) {
        cb.setAttribute('checked', 'checked');
        cb.defaultChecked = true;
      }

      span.appendChild(cb);

      var title = this.convertValueToString(layer) || mxResources.get('background') || 'Background';
      span.setAttribute('title', title);
      mxUtils.write(span, title);
      div.appendChild(span);

      mxEvent.addListener(cb, 'click', function () {
        if (cb.getAttribute('checked') != null) {
          cb.removeAttribute('checked');
        } else {
          cb.setAttribute('checked', 'checked');
        }

        model.setVisible(layer, cb.checked);
      });
    })(model.getChildAt(model.root, i));
  }

  return div;
};

/**
 * Private helper method.
 */
Graph.prototype.replacePlaceholders = function (cell, str, vars, translate) {
  var result = [];

  if (str != null) {
    var last = 0;

    while ((match = this.placeholderPattern.exec(str))) {
      var val = match[0];

      if (val.length > 2 && val != '%label%' && val != '%tooltip%') {
        var tmp = null;

        if (match.index > last && str.charAt(match.index - 1) == '%') {
          tmp = val.substring(1);
        } else {
          var name = val.substring(1, val.length - 1);

          // Workaround for invalid char for getting attribute in older versions of IE
          if (name == 'id') {
            tmp = cell.id;
          } else if (name.indexOf('{') < 0) {
            var current = cell;

            while (tmp == null && current != null) {
              if (current.value != null && typeof current.value == 'object') {
                if (Graph.translateDiagram && Graph.diagramLanguage != null) {
                  tmp = current.getAttribute(name + '_' + Graph.diagramLanguage);
                }

                if (tmp == null) {
                  tmp = current.hasAttribute(name)
                    ? current.getAttribute(name) != null
                      ? current.getAttribute(name)
                      : ''
                    : null;
                }
              }

              current = this.model.getParent(current);
            }
          }

          if (tmp == null) {
            tmp = this.getGlobalVariable(name);
          }

          if (tmp == null && vars != null) {
            tmp = vars[name];
          }
        }

        result.push(str.substring(last, match.index) + (tmp != null ? tmp : val));
        last = match.index + val.length;
      }
    }

    result.push(str.substring(last));
  }

  return result.join('');
};

/**
 * Resolves the given cells in the model and selects them.
 */
Graph.prototype.restoreSelection = function (cells) {
  if (cells != null && cells.length > 0) {
    var temp = [];

    for (var i = 0; i < cells.length; i++) {
      var newCell = this.model.getCell(cells[i].id);

      if (newCell != null) {
        temp.push(newCell);
      }
    }

    this.setSelectionCells(temp);
  } else {
    this.clearSelection();
  }
};

/**
 * Selects cells for connect vertex return value.
 */
Graph.prototype.selectCellsForConnectVertex = function (cells, evt, hoverIcons) {
  // Selects only target vertex if one exists
  if (cells.length == 2 && this.model.isVertex(cells[1])) {
    this.setSelectionCell(cells[1]);
    this.scrollCellToVisible(cells[1]);

    if (hoverIcons != null) {
      // Adds hover icons for cloned vertex or hides icons
      if (mxEvent.isTouchEvent(evt)) {
        hoverIcons.update(hoverIcons.getState(this.view.getState(cells[1])));
      } else {
        hoverIcons.reset();
      }
    }
  } else {
    this.setSelectionCells(cells);
  }
};

/**
 * Never connects children in stack layouts or tables.
 */
Graph.prototype.isCloneConnectSource = function (source) {
  var layout = null;

  if (this.layoutManager != null) {
    layout = this.layoutManager.getLayout(this.model.getParent(source));
  }

  return this.isTableRow(source) || this.isTableCell(source) || (layout != null && layout.constructor == mxStackLayout);
};

/**
 * Adds a connection to the given vertex.
 */
Graph.prototype.connectVertex = function (
  source,
  direction,
  length,
  evt,
  forceClone,
  ignoreCellAt,
  createTarget,
  done
) {
  ignoreCellAt = ignoreCellAt ? ignoreCellAt : false;

  // Ignores relative edge labels
  if (source.geometry.relative && this.model.isEdge(source.parent)) {
    return [];
  }

  // Uses parent for relative child cells
  while (source.geometry.relative && this.model.isVertex(source.parent)) {
    source = source.parent;
  }

  // Handles clone connect sources
  var cloneSource = this.isCloneConnectSource(source);
  var composite = cloneSource ? source : this.getCompositeParent(source);

  var pt =
    source.geometry.relative && source.parent.geometry != null
      ? new mxPoint(source.parent.geometry.width * source.geometry.x, source.parent.geometry.height * source.geometry.y)
      : new mxPoint(composite.geometry.x, composite.geometry.y);

  if (direction == mxConstants.DIRECTION_NORTH) {
    pt.x += composite.geometry.width / 2;
    pt.y -= length;
  } else if (direction == mxConstants.DIRECTION_SOUTH) {
    pt.x += composite.geometry.width / 2;
    pt.y += composite.geometry.height + length;
  } else if (direction == mxConstants.DIRECTION_WEST) {
    pt.x -= length;
    pt.y += composite.geometry.height / 2;
  } else {
    pt.x += composite.geometry.width + length;
    pt.y += composite.geometry.height / 2;
  }

  var parentState = this.view.getState(this.model.getParent(source));
  var s = this.view.scale;
  var t = this.view.translate;
  var dx = t.x * s;
  var dy = t.y * s;

  if (parentState != null && this.model.isVertex(parentState.cell)) {
    dx = parentState.x;
    dy = parentState.y;
  }

  // Workaround for relative child cells
  if (this.model.isVertex(source.parent) && source.geometry.relative) {
    pt.x += source.parent.geometry.x;
    pt.y += source.parent.geometry.y;
  }

  // Checks actual end point of edge for target cell
  var rect = !ignoreCellAt ? new mxRectangle(dx + pt.x * s, dy + pt.y * s).grow(40) : null;
  var tempCells = rect != null ? this.getCells(0, 0, 0, 0, null, null, rect) : null;
  var target = tempCells != null && tempCells.length > 0 ? tempCells.reverse()[0] : null;
  var keepParent = false;

  if (target != null && this.model.isAncestor(target, source)) {
    keepParent = true;
    target = null;
  }

  // Checks for swimlane at drop location
  if (target == null) {
    var temp = this.getSwimlaneAt(dx + pt.x * s, dy + pt.y * s);

    if (temp != null) {
      keepParent = false;
      target = temp;
    }
  }

  // Checks if target or ancestor is locked
  var temp = target;

  while (temp != null) {
    if (this.isCellLocked(temp)) {
      target = null;
      break;
    }

    temp = this.model.getParent(temp);
  }

  // Checks if source and target intersect
  if (target != null) {
    var sourceState = this.view.getState(source);
    var targetState = this.view.getState(target);

    if (sourceState != null && targetState != null && mxUtils.intersects(sourceState, targetState)) {
      target = null;
    }
  }

  var duplicate = !mxEvent.isShiftDown(evt) || mxEvent.isControlDown(evt) || forceClone;

  if (duplicate) {
    if (direction == mxConstants.DIRECTION_NORTH) {
      pt.y -= source.geometry.height / 2;
    } else if (direction == mxConstants.DIRECTION_SOUTH) {
      pt.y += source.geometry.height / 2;
    } else if (direction == mxConstants.DIRECTION_WEST) {
      pt.x -= source.geometry.width / 2;
    } else {
      pt.x += source.geometry.width / 2;
    }
  }

  // Uses connectable parent vertex if one exists
  // TODO: Fix using target as parent for swimlane
  if (target != null && !this.isCellConnectable(target) && !this.isSwimlane(target)) {
    var parent = this.getModel().getParent(target);

    if (this.getModel().isVertex(parent) && this.isCellConnectable(parent)) {
      target = parent;
    }
  }

  if (target == source || this.model.isEdge(target) || (!this.isCellConnectable(target) && !this.isSwimlane(target))) {
    target = null;
  }

  var result = [];
  var swimlane = target != null && this.isSwimlane(target);
  var realTarget = !swimlane ? target : null;

  var execute = mxUtils.bind(this, function (targetCell) {
    if (createTarget == null || targetCell != null || (target == null && cloneSource)) {
      this.model.beginUpdate();
      try {
        if (realTarget == null && duplicate) {
          // Handles relative children
          var cellToClone = targetCell != null ? targetCell : source;
          var geo = this.getCellGeometry(cellToClone);

          while (geo != null && geo.relative) {
            cellToClone = this.getModel().getParent(cellToClone);
            geo = this.getCellGeometry(cellToClone);
          }

          // Handles composite cells for cloning
          cellToClone = cloneSource ? source : this.getCompositeParent(cellToClone);
          realTarget = targetCell != null ? targetCell : this.duplicateCells([cellToClone], false)[0];

          if (targetCell != null) {
            this.addCells([realTarget], this.model.getParent(source), null, null, null, true);
          }

          var geo = this.getCellGeometry(realTarget);

          if (geo != null) {
            geo.x = pt.x - geo.width / 2;
            geo.y = pt.y - geo.height / 2;
          }

          if (swimlane) {
            this.addCells([realTarget], target, null, null, null, true);
            target = null;
          } else if (duplicate && target == null && !keepParent && !cloneSource) {
            this.addCells([realTarget], this.getDefaultParent(), null, null, null, true);
          }
        }

        var edge =
          (mxEvent.isControlDown(evt) && mxEvent.isShiftDown(evt) && duplicate) || (target == null && cloneSource)
            ? null
            : this.insertEdge(
                this.model.getParent(source),
                null,
                '',
                source,
                realTarget,
                this.createCurrentEdgeStyle()
              );

        // Inserts edge before source
        if (edge != null && this.connectionHandler.insertBeforeSource) {
          var index = null;
          var tmp = source;

          while (tmp.parent != null && tmp.geometry != null && tmp.geometry.relative && tmp.parent != edge.parent) {
            tmp = this.model.getParent(tmp);
          }

          if (tmp != null && tmp.parent != null && tmp.parent == edge.parent) {
            var index = tmp.parent.getIndex(tmp);
            this.model.add(tmp.parent, edge, index);
          }
        }

        // Special case: Click on west icon puts clone before cell
        if (
          target == null &&
          realTarget != null &&
          source.parent != null &&
          cloneSource &&
          direction == mxConstants.DIRECTION_WEST
        ) {
          var index = source.parent.getIndex(source);
          this.model.add(source.parent, realTarget, index);
        }

        if (edge != null) {
          result.push(edge);
        }

        if (target == null && realTarget != null) {
          result.push(realTarget);
        }

        if (realTarget == null && edge != null) {
          edge.geometry.setTerminalPoint(pt, false);
        }

        if (edge != null) {
          this.fireEvent(new mxEventObject('cellsInserted', 'cells', [edge]));
        }
      } finally {
        this.model.endUpdate();
      }
    }

    if (done != null) {
      done(result);
    } else {
      return result;
    }
  });

  if (createTarget != null && realTarget == null && duplicate && (target != null || !cloneSource)) {
    createTarget(dx + pt.x * s, dy + pt.y * s, execute);
  } else {
    return execute(realTarget);
  }
};

/**
 * Returns all labels in the diagram as a string.
 */
Graph.prototype.getIndexableText = function () {
  var tmp = document.createElement('div');
  var labels = [];
  var label = '';

  for (var key in this.model.cells) {
    var cell = this.model.cells[key];

    if (this.model.isVertex(cell) || this.model.isEdge(cell)) {
      if (this.isHtmlLabel(cell)) {
        tmp.innerHTML = this.sanitizeHtml(this.getLabel(cell));
        label = mxUtils.extractTextWithWhitespace([tmp]);
      } else {
        label = this.getLabel(cell);
      }

      label = mxUtils.trim(label.replace(/[\x00-\x1F\x7F-\x9F]|\s+/g, ' '));

      if (label.length > 0) {
        labels.push(label);
      }
    }
  }

  return labels.join(' ');
};

/**
 * Returns the label for the given cell.
 */
Graph.prototype.convertValueToString = function (cell) {
  var value = this.model.getValue(cell);

  if (value != null && typeof value == 'object') {
    var result = null;

    if (this.isReplacePlaceholders(cell) && cell.getAttribute('placeholder') != null) {
      var name = cell.getAttribute('placeholder');
      var current = cell;

      while (result == null && current != null) {
        if (current.value != null && typeof current.value == 'object') {
          result = current.hasAttribute(name)
            ? current.getAttribute(name) != null
              ? current.getAttribute(name)
              : ''
            : null;
        }

        current = this.model.getParent(current);
      }
    } else {
      var result = null;

      if (Graph.translateDiagram && Graph.diagramLanguage != null) {
        result = value.getAttribute('label_' + Graph.diagramLanguage);
      }

      if (result == null) {
        result = value.getAttribute('label') || '';
      }
    }

    return result || '';
  }

  return mxGraph.prototype.convertValueToString.apply(this, arguments);
};

/**
 * Returns the link for the given cell.
 */
Graph.prototype.getLinksForState = function (state) {
  if (state != null && state.text != null && state.text.node != null) {
    return state.text.node.getElementsByTagName('a');
  }

  return null;
};

/**
 * Returns the link for the given cell.
 */
Graph.prototype.getLinkForCell = function (cell) {
  if (cell.value != null && typeof cell.value == 'object') {
    var link = cell.value.getAttribute('link');

    // Removes links with leading javascript: protocol
    // TODO: Check more possible attack vectors
    if (link != null && link.toLowerCase().substring(0, 11) === 'javascript:') {
      link = link.substring(11);
    }

    return link;
  }

  return null;
};

/**
 * Overrides label orientation for collapsed swimlanes inside stack and
 * for partial rectangles inside tables.
 */
Graph.prototype.getCellStyle = function (cell) {
  var style = mxGraph.prototype.getCellStyle.apply(this, arguments);

  if (cell != null && this.layoutManager != null) {
    var parent = this.model.getParent(cell);

    if (this.model.isVertex(parent) && this.isCellCollapsed(cell)) {
      var layout = this.layoutManager.getLayout(parent);

      if (layout != null && layout.constructor == mxStackLayout) {
        style[mxConstants.STYLE_HORIZONTAL] = !layout.horizontal;
      }
    }
  }

  return style;
};

/**
 * Disables alternate width persistence for stack layout parents
 */
Graph.prototype.updateAlternateBounds = function (cell, geo, willCollapse) {
  if (cell != null && geo != null && this.layoutManager != null && geo.alternateBounds != null) {
    var layout = this.layoutManager.getLayout(this.model.getParent(cell));

    if (layout != null && layout.constructor == mxStackLayout) {
      if (layout.horizontal) {
        geo.alternateBounds.height = 0;
      } else {
        geo.alternateBounds.width = 0;
      }
    }
  }

  mxGraph.prototype.updateAlternateBounds.apply(this, arguments);
};

/**
 * Adds Shift+collapse/expand and size management for folding inside stack
 */
Graph.prototype.isMoveCellsEvent = function (evt, state) {
  return mxEvent.isShiftDown(evt) || mxUtils.getValue(state.style, 'moveCells', '0') == '1';
};

/**
 * Adds Shift+collapse/expand and size management for folding inside stack
 */
Graph.prototype.foldCells = function (collapse, recurse, cells, checkFoldable, evt) {
  recurse = recurse != null ? recurse : false;

  if (cells == null) {
    cells = this.getFoldableCells(this.getSelectionCells(), collapse);
  }

  if (cells != null) {
    this.model.beginUpdate();

    try {
      mxGraph.prototype.foldCells.apply(this, arguments);

      // Resizes all parent stacks if alt is not pressed
      if (this.layoutManager != null) {
        for (var i = 0; i < cells.length; i++) {
          var state = this.view.getState(cells[i]);
          var geo = this.getCellGeometry(cells[i]);

          if (state != null && geo != null) {
            var dx = Math.round(geo.width - state.width / this.view.scale);
            var dy = Math.round(geo.height - state.height / this.view.scale);

            if (dy != 0 || dx != 0) {
              var parent = this.model.getParent(cells[i]);
              var layout = this.layoutManager.getLayout(parent);

              if (layout == null) {
                // Moves cells to the right and down after collapse/expand
                if (evt != null && this.isMoveCellsEvent(evt, state)) {
                  this.moveSiblings(state, parent, dx, dy);
                }
              } else if (
                (evt == null || !mxEvent.isAltDown(evt)) &&
                layout.constructor == mxStackLayout &&
                !layout.resizeLast
              ) {
                this.resizeParentStacks(parent, layout, dx, dy);
              }
            }
          }
        }
      }
    } finally {
      this.model.endUpdate();
    }

    // Selects cells after folding
    if (this.isEnabled()) {
      this.setSelectionCells(cells);
    }
  }
};

/**
 * Overrides label orientation for collapsed swimlanes inside stack.
 */
Graph.prototype.moveSiblings = function (state, parent, dx, dy) {
  this.model.beginUpdate();
  try {
    var cells = this.getCellsBeyond(state.x, state.y, parent, true, true);

    for (var i = 0; i < cells.length; i++) {
      if (cells[i] != state.cell) {
        var tmp = this.view.getState(cells[i]);
        var geo = this.getCellGeometry(cells[i]);

        if (tmp != null && geo != null) {
          geo = geo.clone();
          geo.translate(
            Math.round(dx * Math.max(0, Math.min(1, (tmp.x - state.x) / state.width))),
            Math.round(dy * Math.max(0, Math.min(1, (tmp.y - state.y) / state.height)))
          );
          this.model.setGeometry(cells[i], geo);
        }
      }
    }
  } finally {
    this.model.endUpdate();
  }
};

/**
 * Overrides label orientation for collapsed swimlanes inside stack.
 */
Graph.prototype.resizeParentStacks = function (parent, layout, dx, dy) {
  if (this.layoutManager != null && layout != null && layout.constructor == mxStackLayout && !layout.resizeLast) {
    this.model.beginUpdate();
    try {
      var dir = layout.horizontal;

      // Bubble resize up for all parent stack layouts with same orientation
      while (
        parent != null &&
        layout != null &&
        layout.constructor == mxStackLayout &&
        layout.horizontal == dir &&
        !layout.resizeLast
      ) {
        var pgeo = this.getCellGeometry(parent);
        var pstate = this.view.getState(parent);

        if (pstate != null && pgeo != null) {
          pgeo = pgeo.clone();

          if (layout.horizontal) {
            pgeo.width += dx + Math.min(0, pstate.width / this.view.scale - pgeo.width);
          } else {
            pgeo.height += dy + Math.min(0, pstate.height / this.view.scale - pgeo.height);
          }

          this.model.setGeometry(parent, pgeo);
        }

        parent = this.model.getParent(parent);
        layout = this.layoutManager.getLayout(parent);
      }
    } finally {
      this.model.endUpdate();
    }
  }
};

/**
 * Disables drill-down for non-swimlanes.
 */
Graph.prototype.isContainer = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  if (this.isSwimlane(cell)) {
    return style['container'] != '0';
  } else {
    return style['container'] == '1';
  }
};

/**
 * Adds a connectable style.
 */
Graph.prototype.isCellConnectable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return style['connectable'] != null
    ? style['connectable'] != '0'
    : mxGraph.prototype.isCellConnectable.apply(this, arguments);
};

/**
 * Adds labelMovable style.
 */
Graph.prototype.isLabelMovable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return style['movableLabel'] != null
    ? style['movableLabel'] != '0'
    : mxGraph.prototype.isLabelMovable.apply(this, arguments);
};

/**
 * Function: selectAll
 *
 * Selects all children of the given parent cell or the children of the
 * default parent if no parent is specified. To select leaf vertices and/or
 * edges use <selectCells>.
 *
 * Parameters:
 *
 * parent - Optional <mxCell> whose children should be selected.
 * Default is <defaultParent>.
 */
Graph.prototype.selectAll = function (parent) {
  parent = parent || this.getDefaultParent();

  if (!this.isCellLocked(parent)) {
    mxGraph.prototype.selectAll.apply(this, arguments);
  }
};

/**
 * Function: selectCells
 *
 * Selects all vertices and/or edges depending on the given boolean
 * arguments recursively, starting at the given parent or the default
 * parent if no parent is specified. Use <selectAll> to select all cells.
 * For vertices, only cells with no children are selected.
 *
 * Parameters:
 *
 * vertices - Boolean indicating if vertices should be selected.
 * edges - Boolean indicating if edges should be selected.
 * parent - Optional <mxCell> that acts as the root of the recursion.
 * Default is <defaultParent>.
 */
Graph.prototype.selectCells = function (vertices, edges, parent) {
  parent = parent || this.getDefaultParent();

  if (!this.isCellLocked(parent)) {
    mxGraph.prototype.selectCells.apply(this, arguments);
  }
};

/**
 * Function: getSwimlaneAt
 *
 * Returns the bottom-most swimlane that intersects the given point (x, y)
 * in the cell hierarchy that starts at the given parent.
 *
 * Parameters:
 *
 * x - X-coordinate of the location to be checked.
 * y - Y-coordinate of the location to be checked.
 * parent - <mxCell> that should be used as the root of the recursion.
 * Default is <defaultParent>.
 */
Graph.prototype.getSwimlaneAt = function (x, y, parent) {
  var result = mxGraph.prototype.getSwimlaneAt.apply(this, arguments);

  if (this.isCellLocked(result)) {
    result = null;
  }

  return result;
};

/**
 * Disables folding for non-swimlanes.
 */
Graph.prototype.isCellFoldable = function (cell) {
  var style = this.getCurrentCellStyle(cell);

  return (
    this.foldingEnabled &&
    (style['treeFolding'] == '1' ||
      (!this.isCellLocked(cell) &&
        ((this.isContainer(cell) && style['collapsible'] != '0') ||
          (!this.isContainer(cell) && style['collapsible'] == '1'))))
  );
};

/**
 * Stops all interactions and clears the selection.
 */
Graph.prototype.reset = function () {
  if (this.isEditing()) {
    this.stopEditing(true);
  }

  this.escape();

  if (!this.isSelectionEmpty()) {
    this.clearSelection();
  }
};

/**
 * Overridden to limit zoom to 1% - 16.000%.
 */
Graph.prototype.zoom = function (factor, center) {
  factor = Math.max(0.01, Math.min(this.view.scale * factor, 160)) / this.view.scale;

  mxGraph.prototype.zoom.apply(this, arguments);
};

/**
 * Function: zoomIn
 *
 * Zooms into the graph by <zoomFactor>.
 */
Graph.prototype.zoomIn = function () {
  // Switches to 1% zoom steps below 15%
  if (this.view.scale < 0.15) {
    this.zoom((this.view.scale + 0.01) / this.view.scale);
  } else {
    // Uses to 5% zoom steps for better grid rendering in webkit
    // and to avoid rounding errors for zoom steps
    this.zoom(Math.round(this.view.scale * this.zoomFactor * 20) / 20 / this.view.scale);
  }
};

/**
 * Function: zoomOut
 *
 * Zooms out of the graph by <zoomFactor>.
 */
Graph.prototype.zoomOut = function () {
  // Switches to 1% zoom steps below 15%
  if (this.view.scale <= 0.15) {
    this.zoom((this.view.scale - 0.01) / this.view.scale);
  } else {
    // Uses to 5% zoom steps for better grid rendering in webkit
    // and to avoid rounding errors for zoom steps
    this.zoom(Math.round(this.view.scale * (1 / this.zoomFactor) * 20) / 20 / this.view.scale);
  }
};

/**
 * Function: fitWindow
 *
 * Sets the current visible rectangle of the window in graph coordinates.
 */
Graph.prototype.fitWindow = function (bounds, border) {
  border = border != null ? border : 10;

  var cw = this.container.clientWidth - border;
  var ch = this.container.clientHeight - border;
  var scale = Math.floor(20 * Math.min(cw / bounds.width, ch / bounds.height)) / 20;
  this.zoomTo(scale);

  if (mxUtils.hasScrollbars(this.container)) {
    var t = this.view.translate;
    this.container.scrollTop = (bounds.y + t.y) * scale - Math.max((ch - bounds.height * scale) / 2 + border / 2, 0);
    this.container.scrollLeft = (bounds.x + t.x) * scale - Math.max((cw - bounds.width * scale) / 2 + border / 2, 0);
  }
};

/**
 * Overrides tooltips to show custom tooltip or metadata.
 */
Graph.prototype.getTooltipForCell = function (cell) {
  var tip = '';

  if (mxUtils.isNode(cell.value)) {
    var tmp = null;

    if (Graph.translateDiagram && Graph.diagramLanguage != null) {
      tmp = cell.value.getAttribute('tooltip_' + Graph.diagramLanguage);
    }

    if (tmp == null) {
      tmp = cell.value.getAttribute('tooltip');
    }

    if (tmp != null) {
      if (tmp != null && this.isReplacePlaceholders(cell)) {
        tmp = this.replacePlaceholders(cell, tmp);
      }

      tip = this.sanitizeHtml(tmp);
    } else {
      var ignored = this.builtInProperties;
      var attrs = cell.value.attributes;
      var temp = [];

      // Hides links in edit mode
      if (this.isEnabled()) {
        ignored.push('link');
      }

      for (var i = 0; i < attrs.length; i++) {
        if (mxUtils.indexOf(ignored, attrs[i].nodeName) < 0 && attrs[i].nodeValue.length > 0) {
          temp.push({ name: attrs[i].nodeName, value: attrs[i].nodeValue });
        }
      }

      // Sorts by name
      temp.sort(function (a, b) {
        if (a.name < b.name) {
          return -1;
        } else if (a.name > b.name) {
          return 1;
        } else {
          return 0;
        }
      });

      for (var i = 0; i < temp.length; i++) {
        if (temp[i].name != 'link' || !this.isCustomLink(temp[i].value)) {
          tip +=
            (temp[i].name != 'link' ? '<b>' + temp[i].name + ':</b> ' : '') +
            mxUtils.htmlEntities(temp[i].value) +
            '\n';
        }
      }

      if (tip.length > 0) {
        tip = tip.substring(0, tip.length - 1);

        if (mxClient.IS_SVG) {
          tip = '<div style="max-width:360px;">' + tip + '</div>';
        }
      }
    }
  }

  return tip;
};

/**
 * Turns the given string into an array.
 */
Graph.prototype.stringToBytes = function (str) {
  return Graph.stringToBytes(str);
};

/**
 * Turns the given array into a string.
 */
Graph.prototype.bytesToString = function (arr) {
  return Graph.bytesToString(arr);
};

/**
 * Returns a base64 encoded version of the compressed outer XML of the given node.
 */
Graph.prototype.compressNode = function (node) {
  return Graph.compressNode(node);
};

/**
 * Returns a base64 encoded version of the compressed string.
 */
Graph.prototype.compress = function (data, deflate) {
  return Graph.compress(data, deflate);
};

/**
 * Returns a decompressed version of the base64 encoded string.
 */
Graph.prototype.decompress = function (data, inflate) {
  return Graph.decompress(data, inflate);
};

/**
 * Redirects to Graph.zapGremlins.
 */
Graph.prototype.zapGremlins = function (text) {
  return Graph.zapGremlins(text);
};

/**
 * Hover icons are used for hover, vertex handler and drag from sidebar.
 */
HoverIcons = function (graph) {
  this.graph = graph;
  this.init();
};

/**
 * Up arrow.
 */
HoverIcons.prototype.arrowSpacing = 2;

/**
 * Delay to switch to another state for overlapping bbox. Default is 500ms.
 */
HoverIcons.prototype.updateDelay = 500;

/**
 * Delay to switch between states. Default is 140ms.
 */
HoverIcons.prototype.activationDelay = 140;

/**
 * Up arrow.
 */
HoverIcons.prototype.currentState = null;

/**
 * Up arrow.
 */
HoverIcons.prototype.activeArrow = null;

/**
 * Up arrow.
 */
HoverIcons.prototype.inactiveOpacity = 15;

/**
 * Up arrow.
 */
HoverIcons.prototype.cssCursor = 'copy';

/**
 * Whether to hide arrows that collide with vertices.
 * LATER: Add keyboard override, touch support.
 */
HoverIcons.prototype.checkCollisions = true;

/**
 * Up arrow.
 */
HoverIcons.prototype.arrowFill = '#29b6f2';

/**
 * Up arrow.
 */
HoverIcons.prototype.triangleUp = !mxClient.IS_SVG
  ? new mxImage(IMAGE_PATH + '/triangle-up.png', 26, 14)
  : Graph.createSvgImage(
      18,
      28,
      '<path d="m 6 26 L 12 26 L 12 12 L 18 12 L 9 1 L 1 12 L 6 12 z" ' +
        'stroke="#fff" fill="' +
        HoverIcons.prototype.arrowFill +
        '"/>'
    );

/**
 * Right arrow.
 */
HoverIcons.prototype.triangleRight = !mxClient.IS_SVG
  ? new mxImage(IMAGE_PATH + '/triangle-right.png', 14, 26)
  : Graph.createSvgImage(
      26,
      18,
      '<path d="m 1 6 L 14 6 L 14 1 L 26 9 L 14 18 L 14 12 L 1 12 z" ' +
        'stroke="#fff" fill="' +
        HoverIcons.prototype.arrowFill +
        '"/>'
    );

/**
 * Down arrow.
 */
HoverIcons.prototype.triangleDown = !mxClient.IS_SVG
  ? new mxImage(IMAGE_PATH + '/triangle-down.png', 26, 14)
  : Graph.createSvgImage(
      18,
      26,
      '<path d="m 6 1 L 6 14 L 1 14 L 9 26 L 18 14 L 12 14 L 12 1 z" ' +
        'stroke="#fff" fill="' +
        HoverIcons.prototype.arrowFill +
        '"/>'
    );

/**
 * Left arrow.
 */
HoverIcons.prototype.triangleLeft = !mxClient.IS_SVG
  ? new mxImage(IMAGE_PATH + '/triangle-left.png', 14, 26)
  : Graph.createSvgImage(
      28,
      18,
      '<path d="m 1 9 L 12 1 L 12 6 L 26 6 L 26 12 L 12 12 L 12 18 z" ' +
        'stroke="#fff" fill="' +
        HoverIcons.prototype.arrowFill +
        '"/>'
    );

/**
 * Round target.
 */
HoverIcons.prototype.roundDrop = !mxClient.IS_SVG
  ? new mxImage(IMAGE_PATH + '/round-drop.png', 26, 26)
  : Graph.createSvgImage(
      26,
      26,
      '<circle cx="13" cy="13" r="12" ' + 'stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '"/>'
    );

/**
 * Refresh target.
 */
HoverIcons.prototype.refreshTarget = new mxImage(
  mxClient.IS_SVG
    ? 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjM2cHgiIGhlaWdodD0iMzZweCI+PGVsbGlwc2UgZmlsbD0iIzI5YjZmMiIgY3g9IjEyIiBjeT0iMTIiIHJ4PSIxMiIgcnk9IjEyIi8+PHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjgpIHRyYW5zbGF0ZSgyLjQsIDIuNCkiIHN0cm9rZT0iI2ZmZiIgZmlsbD0iI2ZmZiIgZD0iTTEyIDZ2M2w0LTQtNC00djNjLTQuNDIgMC04IDMuNTgtOCA4IDAgMS41Ny40NiAzLjAzIDEuMjQgNC4yNkw2LjcgMTQuOGMtLjQ1LS44My0uNy0xLjc5LS43LTIuOCAwLTMuMzEgMi42OS02IDYtNnptNi43NiAxLjc0TDE3LjMgOS4yYy40NC44NC43IDEuNzkuNyAyLjggMCAzLjMxLTIuNjkgNi02IDZ2LTNsLTQgNCA0IDR2LTNjNC40MiAwIDgtMy41OCA4LTggMC0xLjU3LS40Ni0zLjAzLTEuMjQtNC4yNnoiLz48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+Cg=='
    : IMAGE_PATH + '/refresh.png',
  38,
  38
);

/**
 * Tolerance for hover icon clicks.
 */
HoverIcons.prototype.tolerance = mxClient.IS_TOUCH ? 6 : 0;

/**
 *
 */
HoverIcons.prototype.init = function () {
  this.arrowUp = this.createArrow(this.triangleUp, mxResources.get('plusTooltip'));
  this.arrowRight = this.createArrow(this.triangleRight, mxResources.get('plusTooltip'));
  this.arrowDown = this.createArrow(this.triangleDown, mxResources.get('plusTooltip'));
  this.arrowLeft = this.createArrow(this.triangleLeft, mxResources.get('plusTooltip'));

  this.elts = [this.arrowUp, this.arrowRight, this.arrowDown, this.arrowLeft];

  this.resetHandler = mxUtils.bind(this, function () {
    this.reset();
  });

  this.repaintHandler = mxUtils.bind(this, function () {
    this.repaint();
  });

  this.graph.selectionModel.addListener(mxEvent.CHANGE, this.resetHandler);
  this.graph.model.addListener(mxEvent.CHANGE, this.repaintHandler);
  this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler);
  this.graph.view.addListener(mxEvent.TRANSLATE, this.repaintHandler);
  this.graph.view.addListener(mxEvent.SCALE, this.repaintHandler);
  this.graph.view.addListener(mxEvent.DOWN, this.repaintHandler);
  this.graph.view.addListener(mxEvent.UP, this.repaintHandler);
  this.graph.addListener(mxEvent.ROOT, this.repaintHandler);
  this.graph.addListener(mxEvent.ESCAPE, this.resetHandler);
  mxEvent.addListener(this.graph.container, 'scroll', this.resetHandler);

  // Resets the mouse point on escape
  this.graph.addListener(
    mxEvent.ESCAPE,
    mxUtils.bind(this, function () {
      this.mouseDownPoint = null;
    })
  );

  // Removes hover icons if mouse leaves the container
  mxEvent.addListener(
    this.graph.container,
    'mouseleave',
    mxUtils.bind(this, function (evt) {
      // Workaround for IE11 firing mouseleave for touch in diagram
      if (evt.relatedTarget != null && mxEvent.getSource(evt) == this.graph.container) {
        this.setDisplay('none');
      }
    })
  );

  // Resets current state when in-place editor starts
  this.graph.addListener(
    mxEvent.START_EDITING,
    mxUtils.bind(this, function (evt) {
      this.reset();
    })
  );

  // Resets current state after update of selection state for touch events
  var graphClick = this.graph.click;
  this.graph.click = mxUtils.bind(this, function (me) {
    graphClick.apply(this.graph, arguments);

    if (
      this.currentState != null &&
      !this.graph.isCellSelected(this.currentState.cell) &&
      mxEvent.isTouchEvent(me.getEvent()) &&
      !this.graph.model.isVertex(me.getCell())
    ) {
      this.reset();
    }
  });

  // Checks if connection handler was active in mouse move
  // as workaround for possible double connection inserted
  var connectionHandlerActive = false;

  // Implements a listener for hover and click handling
  this.graph.addMouseListener({
    mouseDown: mxUtils.bind(this, function (sender, me) {
      connectionHandlerActive = false;
      var evt = me.getEvent();

      if (this.isResetEvent(evt)) {
        this.reset();
      } else if (!this.isActive()) {
        var state = this.getState(me.getState());

        if (state != null || !mxEvent.isTouchEvent(evt)) {
          this.update(state);
        }
      }

      this.setDisplay('none');
    }),
    mouseMove: mxUtils.bind(this, function (sender, me) {
      var evt = me.getEvent();

      if (this.isResetEvent(evt)) {
        this.reset();
      } else if (!this.graph.isMouseDown && !mxEvent.isTouchEvent(evt)) {
        this.update(this.getState(me.getState()), me.getGraphX(), me.getGraphY());
      }

      if (this.graph.connectionHandler != null && this.graph.connectionHandler.shape != null) {
        connectionHandlerActive = true;
      }
    }),
    mouseUp: mxUtils.bind(this, function (sender, me) {
      var evt = me.getEvent();
      var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));

      if (this.isResetEvent(evt)) {
        this.reset();
      } else if (this.isActive() && !connectionHandlerActive && this.mouseDownPoint != null) {
        this.click(this.currentState, this.getDirection(), me);
      } else if (this.isActive()) {
        // Selects target vertex after drag and clone if not only new edge was inserted
        if (this.graph.getSelectionCount() != 1 || !this.graph.model.isEdge(this.graph.getSelectionCell())) {
          this.update(this.getState(this.graph.view.getState(this.graph.getCellAt(me.getGraphX(), me.getGraphY()))));
        } else {
          this.reset();
        }
      } else if (
        mxEvent.isTouchEvent(evt) ||
        (this.bbox != null && mxUtils.contains(this.bbox, me.getGraphX(), me.getGraphY()))
      ) {
        // Shows existing hover icons if inside bounding box
        this.setDisplay('');
        this.repaint();
      } else if (!mxEvent.isTouchEvent(evt)) {
        this.reset();
      }

      connectionHandlerActive = false;
      this.resetActiveArrow();
    }),
  });
};

/**
 *
 */
HoverIcons.prototype.isResetEvent = function (evt, allowShift) {
  return (
    mxEvent.isAltDown(evt) ||
    (this.activeArrow == null && mxEvent.isShiftDown(evt)) ||
    (mxEvent.isPopupTrigger(evt) && !this.graph.isCloneEvent(evt))
  );
};

/**
 *
 */
HoverIcons.prototype.createArrow = function (img, tooltip) {
  var arrow = null;

  if (mxClient.IS_IE && !mxClient.IS_SVG) {
    // Workaround for PNG images in IE6
    if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat') {
      arrow = document.createElement(mxClient.VML_PREFIX + ':image');
      arrow.setAttribute('src', img.src);
      arrow.style.borderStyle = 'none';
    } else {
      arrow = document.createElement('div');
      arrow.style.backgroundImage = 'url(' + img.src + ')';
      arrow.style.backgroundPosition = 'center';
      arrow.style.backgroundRepeat = 'no-repeat';
    }

    arrow.style.width = img.width + 4 + 'px';
    arrow.style.height = img.height + 4 + 'px';
    arrow.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block';
  } else {
    arrow = mxUtils.createImage(img.src);
    arrow.style.width = img.width + 'px';
    arrow.style.height = img.height + 'px';
    arrow.style.padding = this.tolerance + 'px';
  }

  if (tooltip != null) {
    arrow.setAttribute('title', tooltip);
  }

  arrow.style.position = 'absolute';
  arrow.style.cursor = this.cssCursor;

  mxEvent.addGestureListeners(
    arrow,
    mxUtils.bind(this, function (evt) {
      if (this.currentState != null && !this.isResetEvent(evt)) {
        this.mouseDownPoint = mxUtils.convertPoint(
          this.graph.container,
          mxEvent.getClientX(evt),
          mxEvent.getClientY(evt)
        );
        this.drag(evt, this.mouseDownPoint.x, this.mouseDownPoint.y);
        this.activeArrow = arrow;
        this.setDisplay('none');
        mxEvent.consume(evt);
      }
    })
  );

  // Captures mouse events as events on graph
  mxEvent.redirectMouseEvents(arrow, this.graph, this.currentState);

  mxEvent.addListener(
    arrow,
    'mouseenter',
    mxUtils.bind(this, function (evt) {
      // Workaround for Firefox firing mouseenter on touchend
      if (mxEvent.isMouseEvent(evt)) {
        if (this.activeArrow != null && this.activeArrow != arrow) {
          mxUtils.setOpacity(this.activeArrow, this.inactiveOpacity);
        }

        this.graph.connectionHandler.constraintHandler.reset();
        mxUtils.setOpacity(arrow, 100);
        this.activeArrow = arrow;
      }
    })
  );

  mxEvent.addListener(
    arrow,
    'mouseleave',
    mxUtils.bind(this, function (evt) {
      // Workaround for IE11 firing this event on touch
      if (!this.graph.isMouseDown) {
        this.resetActiveArrow();
      }
    })
  );

  return arrow;
};

/**
 *
 */
HoverIcons.prototype.resetActiveArrow = function () {
  if (this.activeArrow != null) {
    mxUtils.setOpacity(this.activeArrow, this.inactiveOpacity);
    this.activeArrow = null;
  }
};

/**
 *
 */
HoverIcons.prototype.getDirection = function () {
  var dir = mxConstants.DIRECTION_EAST;

  if (this.activeArrow == this.arrowUp) {
    dir = mxConstants.DIRECTION_NORTH;
  } else if (this.activeArrow == this.arrowDown) {
    dir = mxConstants.DIRECTION_SOUTH;
  } else if (this.activeArrow == this.arrowLeft) {
    dir = mxConstants.DIRECTION_WEST;
  }

  return dir;
};

/**
 *
 */
HoverIcons.prototype.visitNodes = function (visitor) {
  for (var i = 0; i < this.elts.length; i++) {
    if (this.elts[i] != null) {
      visitor(this.elts[i]);
    }
  }
};

/**
 *
 */
HoverIcons.prototype.removeNodes = function () {
  this.visitNodes(function (elt) {
    if (elt.parentNode != null) {
      elt.parentNode.removeChild(elt);
    }
  });
};

/**
 *
 */
HoverIcons.prototype.setDisplay = function (display) {
  this.visitNodes(function (elt) {
    elt.style.display = display;
  });
};

/**
 *
 */
HoverIcons.prototype.isActive = function () {
  return this.activeArrow != null && this.currentState != null;
};

/**
 *
 */
HoverIcons.prototype.drag = function (evt, x, y) {
  this.graph.popupMenuHandler.hideMenu();
  this.graph.stopEditing(false);

  // Checks if state was removed in call to stopEditing above
  if (this.currentState != null) {
    this.graph.connectionHandler.start(this.currentState, x, y);
    this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
    this.graph.isMouseDown = true;

    // Hides handles for selection cell
    var handler = this.graph.selectionCellsHandler.getHandler(this.currentState.cell);

    if (handler != null) {
      handler.setHandlesVisible(false);
    }

    // Ctrl+shift drag sets source constraint
    var es = this.graph.connectionHandler.edgeState;

    if (
      evt != null &&
      mxEvent.isShiftDown(evt) &&
      mxEvent.isControlDown(evt) &&
      es != null &&
      mxUtils.getValue(es.style, mxConstants.STYLE_EDGE, null) === 'orthogonalEdgeStyle'
    ) {
      var direction = this.getDirection();
      es.cell.style = mxUtils.setStyle(es.cell.style, 'sourcePortConstraint', direction);
      es.style['sourcePortConstraint'] = direction;
    }
  }
};

/**
 *
 */
HoverIcons.prototype.getStateAt = function (state, x, y) {
  return this.graph.view.getState(this.graph.getCellAt(x, y));
};

/**
 *
 */
HoverIcons.prototype.click = function (state, dir, me) {
  var evt = me.getEvent();
  var x = me.getGraphX();
  var y = me.getGraphY();

  var tmp = this.getStateAt(state, x, y);

  if (
    tmp != null &&
    this.graph.model.isEdge(tmp.cell) &&
    !this.graph.isCloneEvent(evt) &&
    (tmp.getVisibleTerminalState(true) == state || tmp.getVisibleTerminalState(false) == state)
  ) {
    this.graph.setSelectionCell(tmp.cell);
    this.reset();
  } else if (state != null) {
    this.execute(state, dir, me);
  }

  me.consume();
};

/**
 *
 */
HoverIcons.prototype.execute = function (state, dir, me) {
  var evt = me.getEvent();

  this.graph.selectCellsForConnectVertex(
    this.graph.connectVertex(
      state.cell,
      dir,
      this.graph.defaultEdgeLength,
      evt,
      this.graph.isCloneEvent(evt),
      this.graph.isCloneEvent(evt)
    ),
    evt,
    this
  );
};

/**
 *
 */
HoverIcons.prototype.reset = function (clearTimeout) {
  clearTimeout = clearTimeout == null ? true : clearTimeout;

  if (clearTimeout && this.updateThread != null) {
    window.clearTimeout(this.updateThread);
  }

  this.mouseDownPoint = null;
  this.currentState = null;
  this.activeArrow = null;
  this.removeNodes();
  this.bbox = null;
};

/**
 *
 */
HoverIcons.prototype.repaint = function () {
  this.bbox = null;

  if (this.currentState != null) {
    // Checks if cell was deleted
    this.currentState = this.getState(this.currentState);

    // Cell was deleted
    if (
      this.currentState != null &&
      this.graph.model.isVertex(this.currentState.cell) &&
      this.graph.isCellConnectable(this.currentState.cell)
    ) {
      var bds = mxRectangle.fromRectangle(this.currentState);

      // Uses outer bounding box to take rotation into account
      if (this.currentState.shape != null && this.currentState.shape.boundingBox != null) {
        bds = mxRectangle.fromRectangle(this.currentState.shape.boundingBox);
      }

      bds.grow(this.graph.tolerance);
      bds.grow(this.arrowSpacing);

      var handler = this.graph.selectionCellsHandler.getHandler(this.currentState.cell);

      if (this.graph.isTableRow(this.currentState.cell)) {
        handler = this.graph.selectionCellsHandler.getHandler(this.graph.model.getParent(this.currentState.cell));
      }

      var rotationBbox = null;

      if (handler != null) {
        bds.x -= handler.horizontalOffset / 2;
        bds.y -= handler.verticalOffset / 2;
        bds.width += handler.horizontalOffset;
        bds.height += handler.verticalOffset;

        // Adds bounding box of rotation handle to avoid overlap
        if (
          handler.rotationShape != null &&
          handler.rotationShape.node != null &&
          handler.rotationShape.node.style.visibility != 'hidden' &&
          handler.rotationShape.node.style.display != 'none' &&
          handler.rotationShape.boundingBox != null
        ) {
          rotationBbox = handler.rotationShape.boundingBox;
        }
      }

      // Positions arrows avoid collisions with rotation handle
      var positionArrow = mxUtils.bind(this, function (arrow, x, y) {
        if (rotationBbox != null) {
          var bbox = new mxRectangle(x, y, arrow.clientWidth, arrow.clientHeight);

          if (mxUtils.intersects(bbox, rotationBbox)) {
            if (arrow == this.arrowUp) {
              y -= bbox.y + bbox.height - rotationBbox.y;
            } else if (arrow == this.arrowRight) {
              x += rotationBbox.x + rotationBbox.width - bbox.x;
            } else if (arrow == this.arrowDown) {
              y += rotationBbox.y + rotationBbox.height - bbox.y;
            } else if (arrow == this.arrowLeft) {
              x -= bbox.x + bbox.width - rotationBbox.x;
            }
          }
        }

        arrow.style.left = x + 'px';
        arrow.style.top = y + 'px';
        mxUtils.setOpacity(arrow, this.inactiveOpacity);
      });

      positionArrow(
        this.arrowUp,
        Math.round(this.currentState.getCenterX() - this.triangleUp.width / 2 - this.tolerance),
        Math.round(bds.y - this.triangleUp.height - this.tolerance)
      );

      positionArrow(
        this.arrowRight,
        Math.round(bds.x + bds.width - this.tolerance),
        Math.round(this.currentState.getCenterY() - this.triangleRight.height / 2 - this.tolerance)
      );

      positionArrow(this.arrowDown, parseInt(this.arrowUp.style.left), Math.round(bds.y + bds.height - this.tolerance));

      positionArrow(
        this.arrowLeft,
        Math.round(bds.x - this.triangleLeft.width - this.tolerance),
        parseInt(this.arrowRight.style.top)
      );

      if (this.checkCollisions) {
        var right = this.graph.getCellAt(
          bds.x + bds.width + this.triangleRight.width / 2,
          this.currentState.getCenterY()
        );
        var left = this.graph.getCellAt(bds.x - this.triangleLeft.width / 2, this.currentState.getCenterY());
        var top = this.graph.getCellAt(this.currentState.getCenterX(), bds.y - this.triangleUp.height / 2);
        var bottom = this.graph.getCellAt(
          this.currentState.getCenterX(),
          bds.y + bds.height + this.triangleDown.height / 2
        );

        // Shows hover icons large cell is behind all directions of current cell
        if (right != null && right == left && left == top && top == bottom) {
          right = null;
          left = null;
          top = null;
          bottom = null;
        }

        var currentGeo = this.graph.getCellGeometry(this.currentState.cell);

        var checkCollision = mxUtils.bind(this, function (cell, arrow) {
          var geo = this.graph.model.isVertex(cell) && this.graph.getCellGeometry(cell);

          // Ignores collision if vertex is more than 3 times the size of this vertex
          if (
            cell != null &&
            !this.graph.model.isAncestor(cell, this.currentState.cell) &&
            !this.graph.isSwimlane(cell) &&
            (geo == null ||
              currentGeo == null ||
              (geo.height < 3 * currentGeo.height && geo.width < 3 * currentGeo.width))
          ) {
            arrow.style.visibility = 'hidden';
          } else {
            arrow.style.visibility = 'visible';
          }
        });

        checkCollision(right, this.arrowRight);
        checkCollision(left, this.arrowLeft);
        checkCollision(top, this.arrowUp);
        checkCollision(bottom, this.arrowDown);
      } else {
        this.arrowLeft.style.visibility = 'visible';
        this.arrowRight.style.visibility = 'visible';
        this.arrowUp.style.visibility = 'visible';
        this.arrowDown.style.visibility = 'visible';
      }

      if (this.graph.tooltipHandler.isEnabled()) {
        this.arrowLeft.setAttribute('title', mxResources.get('plusTooltip'));
        this.arrowRight.setAttribute('title', mxResources.get('plusTooltip'));
        this.arrowUp.setAttribute('title', mxResources.get('plusTooltip'));
        this.arrowDown.setAttribute('title', mxResources.get('plusTooltip'));
      } else {
        this.arrowLeft.removeAttribute('title');
        this.arrowRight.removeAttribute('title');
        this.arrowUp.removeAttribute('title');
        this.arrowDown.removeAttribute('title');
      }
    } else {
      this.reset();
    }

    // Updates bounding box
    if (this.currentState != null) {
      this.bbox = this.computeBoundingBox();

      // Adds tolerance for hover
      if (this.bbox != null) {
        this.bbox.grow(10);
      }
    }
  }
};

/**
 *
 */
HoverIcons.prototype.computeBoundingBox = function () {
  var bbox = !this.graph.model.isEdge(this.currentState.cell) ? mxRectangle.fromRectangle(this.currentState) : null;

  this.visitNodes(function (elt) {
    if (elt.parentNode != null) {
      var tmp = new mxRectangle(elt.offsetLeft, elt.offsetTop, elt.offsetWidth, elt.offsetHeight);

      if (bbox == null) {
        bbox = tmp;
      } else {
        bbox.add(tmp);
      }
    }
  });

  return bbox;
};

/**
 *
 */
HoverIcons.prototype.getState = function (state) {
  if (state != null) {
    var cell = state.cell;

    if (!this.graph.getModel().contains(cell)) {
      state = null;
    } else {
      // Uses connectable parent vertex if child is not connectable
      if (this.graph.getModel().isVertex(cell) && !this.graph.isCellConnectable(cell)) {
        var parent = this.graph.getModel().getParent(cell);

        if (this.graph.getModel().isVertex(parent) && this.graph.isCellConnectable(parent)) {
          cell = parent;
        }
      }

      // Ignores locked cells and edges
      if (this.graph.isCellLocked(cell) || this.graph.model.isEdge(cell)) {
        cell = null;
      }

      state = this.graph.view.getState(cell);

      if (state != null && state.style == null) {
        state = null;
      }
    }
  }

  return state;
};

/**
 *
 */
HoverIcons.prototype.update = function (state, x, y) {
  if (
    !this.graph.connectionArrowsEnabled ||
    (state != null && mxUtils.getValue(state.style, 'allowArrows', '1') == '0')
  ) {
    this.reset();
  } else {
    if (
      state != null &&
      state.cell.geometry != null &&
      state.cell.geometry.relative &&
      this.graph.model.isEdge(state.cell.parent)
    ) {
      state = null;
    }

    var timeOnTarget = null;

    // Time on target
    if (this.prev != state || this.isActive()) {
      this.startTime = new Date().getTime();
      this.prev = state;
      timeOnTarget = 0;

      if (this.updateThread != null) {
        window.clearTimeout(this.updateThread);
      }

      if (state != null) {
        // Starts timer to update current state with no mouse events
        this.updateThread = window.setTimeout(
          mxUtils.bind(this, function () {
            if (!this.isActive() && !this.graph.isMouseDown && !this.graph.panningHandler.isActive()) {
              this.prev = state;
              this.update(state, x, y);
            }
          }),
          this.updateDelay + 10
        );
      }
    } else if (this.startTime != null) {
      timeOnTarget = new Date().getTime() - this.startTime;
    }

    this.setDisplay('');

    if (
      this.currentState != null &&
      this.currentState != state &&
      timeOnTarget < this.activationDelay &&
      this.bbox != null &&
      !mxUtils.contains(this.bbox, x, y)
    ) {
      this.reset(false);
    } else if (this.currentState != null || timeOnTarget > this.activationDelay) {
      if (
        this.currentState != state &&
        ((timeOnTarget > this.updateDelay && state != null) ||
          this.bbox == null ||
          x == null ||
          y == null ||
          !mxUtils.contains(this.bbox, x, y))
      ) {
        if (state != null && this.graph.isEnabled()) {
          this.removeNodes();
          this.setCurrentState(state);
          this.repaint();

          // Resets connection points on other focused cells
          if (this.graph.connectionHandler.constraintHandler.currentFocus != state) {
            this.graph.connectionHandler.constraintHandler.reset();
          }
        } else {
          this.reset();
        }
      }
    }
  }
};

/**
 *
 */
HoverIcons.prototype.setCurrentState = function (state) {
  if (state.style['portConstraint'] != 'eastwest') {
    this.graph.container.appendChild(this.arrowUp);
    this.graph.container.appendChild(this.arrowDown);
  }

  this.graph.container.appendChild(this.arrowRight);
  this.graph.container.appendChild(this.arrowLeft);
  this.currentState = state;
};

/**
 * Returns true if the given cell is a table.
 */
Graph.prototype.createParent = function (parent, child, childCount, dx, dy) {
  parent = this.cloneCell(parent);

  for (var i = 0; i < childCount; i++) {
    var clone = this.cloneCell(child);
    var geo = this.getCellGeometry(clone);

    if (geo != null) {
      geo.x += i * dx;
      geo.y += i * dy;
    }

    parent.insert(clone);
  }

  return parent;
};

/**
 * Returns true if the given cell is a table.
 */
Graph.prototype.createTable = function (rowCount, colCount, w, h, title, startSize, tableStyle, rowStyle, cellStyle) {
  w = w != null ? w : 60;
  h = h != null ? h : 40;
  startSize = startSize != null ? startSize : 30;
  tableStyle =
    tableStyle != null
      ? tableStyle
      : 'shape=table;html=1;whiteSpace=wrap;startSize=' +
        (title != null ? startSize : '0') +
        ';container=1;collapsible=0;childLayout=tableLayout;';
  rowStyle =
    rowStyle != null
      ? rowStyle
      : 'shape=partialRectangle;html=1;whiteSpace=wrap;collapsible=0;dropTarget=0;' +
        'pointerEvents=0;fillColor=none;top=0;left=0;bottom=0;right=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;';
  cellStyle =
    cellStyle != null
      ? cellStyle
      : 'shape=partialRectangle;html=1;whiteSpace=wrap;connectable=0;' +
        'overflow=hidden;fillColor=none;top=0;left=0;bottom=0;right=0;';

  return this.createParent(
    this.createVertex(
      null,
      null,
      title != null ? title : '',
      0,
      0,
      colCount * w,
      rowCount * h + (title != null ? startSize : 0),
      tableStyle
    ),
    this.createParent(
      this.createVertex(null, null, '', 0, 0, colCount * w, h, rowStyle),
      this.createVertex(null, null, '', 0, 0, w, h, cellStyle),
      colCount,
      w,
      0
    ),
    rowCount,
    0,
    h
  );
};

/**
 * Sets the values for the cells and rows in the given table and returns the table.
 */
Graph.prototype.setTableValues = function (table, values, rowValues) {
  var rows = this.model.getChildCells(table, true);

  for (var i = 0; i < rows.length; i++) {
    if (rowValues != null) {
      rows[i].value = rowValues[i];
    }

    if (values != null) {
      var cells = this.model.getChildCells(rows[i], true);

      for (var j = 0; j < cells.length; j++) {
        if (values[i][j] != null) {
          cells[j].value = values[i][j];
        }
      }
    }
  }

  return table;
};

/**
 *
 */
Graph.prototype.createCrossFunctionalSwimlane = function (
  rowCount,
  colCount,
  w,
  h,
  startSize,
  tableStyle,
  rowStyle,
  firstCellStyle,
  cellStyle
) {
  w = w != null ? w : 120;
  h = h != null ? h : 120;
  startSize = startSize != null ? startSize : 40;

  var s = 'html=1;whiteSpace=wrap;collapsible=0;recursiveResize=0;expand=0;pointerEvents=0;';
  tableStyle =
    tableStyle != null
      ? tableStyle
      : 'shape=table;childLayout=tableLayout;' + 'rowLines=0;columnLines=0;startSize=' + startSize + ';' + s;
  rowStyle =
    rowStyle != null
      ? rowStyle
      : 'swimlane;horizontal=0;points=[[0,0.5],[1,0.5]];' + 'portConstraint=eastwest;startSize=' + startSize + ';' + s;
  firstCellStyle = firstCellStyle != null ? firstCellStyle : 'swimlane;connectable=0;startSize=40;' + s;
  cellStyle = cellStyle != null ? cellStyle : 'swimlane;connectable=0;startSize=0;' + s;

  var table = this.createVertex(null, null, '', 0, 0, colCount * w, rowCount * h, tableStyle);
  var t = mxUtils.getValue(this.getCellStyle(table), mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE);
  table.geometry.width += t;
  table.geometry.height += t;

  var row = this.createVertex(null, null, '', 0, t, colCount * w + t, h, rowStyle);
  table.insert(this.createParent(row, this.createVertex(null, null, '', t, 0, w, h, firstCellStyle), colCount, w, 0));

  if (rowCount > 1) {
    row.geometry.y = h + t;

    return this.createParent(
      table,
      this.createParent(row, this.createVertex(null, null, '', t, 0, w, h, cellStyle), colCount, w, 0),
      rowCount - 1,
      0,
      h
    );
  } else {
    return table;
  }
};

/**
 * Returns true if the given cell is a table cell.
 */
Graph.prototype.isTableCell = function (cell) {
  return this.model.isVertex(cell) && this.isTableRow(this.model.getParent(cell));
};

/**
 * Returns true if the given cell is a table row.
 */
Graph.prototype.isTableRow = function (cell) {
  return this.model.isVertex(cell) && this.isTable(this.model.getParent(cell));
};

/**
 * Returns true if the given cell is a table.
 */
Graph.prototype.isTable = function (cell) {
  var style = this.getCellStyle(cell);

  return style != null && style['childLayout'] == 'tableLayout';
};

/**
 * Updates the row and table heights.
 */
Graph.prototype.setTableRowHeight = function (row, dy, extend) {
  extend = extend != null ? extend : true;
  var model = this.getModel();

  model.beginUpdate();
  try {
    var rgeo = this.getCellGeometry(row);

    // Sets height of row
    if (rgeo != null) {
      rgeo = rgeo.clone();
      rgeo.height += dy;
      model.setGeometry(row, rgeo);

      var table = model.getParent(row);
      var rows = model.getChildCells(table, true);

      // Shifts and resizes neighbor row
      if (!extend) {
        var index = mxUtils.indexOf(rows, row);

        if (index < rows.length - 1) {
          var nextRow = rows[index + 1];
          var geo = this.getCellGeometry(nextRow);

          if (geo != null) {
            geo = geo.clone();
            geo.y += dy;
            geo.height -= dy;

            model.setGeometry(nextRow, geo);
          }
        }
      }

      // Updates height of table
      var tgeo = this.getCellGeometry(table);

      if (tgeo != null) {
        // Always extends for last row
        if (!extend) {
          extend = row == rows[rows.length - 1];
        }

        if (extend) {
          tgeo = tgeo.clone();
          tgeo.height += dy;
          model.setGeometry(table, tgeo);
        }
      }

      if (this.layoutManager != null) {
        this.layoutManager.executeLayout(table, true);
      }
    }
  } finally {
    model.endUpdate();
  }
};

/**
 * Updates column width and row height.
 */
Graph.prototype.setTableColumnWidth = function (col, dx, extend) {
  extend = extend != null ? extend : false;

  var model = this.getModel();
  var row = model.getParent(col);
  var table = model.getParent(row);
  var cells = model.getChildCells(row, true);
  var index = mxUtils.indexOf(cells, col);
  var lastColumn = index == cells.length - 1;

  model.beginUpdate();
  try {
    // Sets width of child cell
    var rows = model.getChildCells(table, true);

    for (var i = 0; i < rows.length; i++) {
      row = rows[i];
      cells = model.getChildCells(row, true);
      var cell = cells[index];
      var geo = this.getCellGeometry(cell);

      if (geo != null) {
        geo = geo.clone();
        geo.width += dx;
        model.setGeometry(cell, geo);
      }

      // Shifts and resizes neighbor column
      if (index < cells.length - 1) {
        cell = cells[index + 1];
        var geo = this.getCellGeometry(cell);

        if (geo != null) {
          geo = geo.clone();
          geo.x += dx;

          if (!extend) {
            geo.width -= dx;
          }

          model.setGeometry(cell, geo);
        }
      }
    }

    if (lastColumn || extend) {
      // Updates width of table
      var tgeo = this.getCellGeometry(table);

      if (tgeo != null) {
        tgeo = tgeo.clone();
        tgeo.width += dx;
        model.setGeometry(table, tgeo);
      }
    }

    if (this.layoutManager != null) {
      this.layoutManager.executeLayout(table, true);
    }
  } finally {
    model.endUpdate();
  }
};

/**
 * Special Layout for tables.
 */
function TableLayout(graph) {
  mxGraphLayout.call(this, graph);
}

/**
 * Extends mxGraphLayout.
 */
TableLayout.prototype = new mxStackLayout();
TableLayout.prototype.constructor = TableLayout;

/**
 * Function: isHorizontal
 *
 * Overrides stack layout to handle row reorder.
 */
TableLayout.prototype.isHorizontal = function () {
  return false;
};

/**
 * Function: isVertexIgnored
 *
 * Overrides to allow for table rows and cells.
 */
TableLayout.prototype.isVertexIgnored = function (vertex) {
  return !this.graph.getModel().isVertex(vertex) || !this.graph.isCellVisible(vertex);
};

/**
 * Function: getSize
 *
 * Returns the total vertical or horizontal size of the given cells.
 */
TableLayout.prototype.getSize = function (cells, horizontal) {
  var total = 0;

  for (var i = 0; i < cells.length; i++) {
    if (!this.isVertexIgnored(cells[i])) {
      var geo = this.graph.getCellGeometry(cells[i]);

      if (geo != null) {
        total += horizontal ? geo.width : geo.height;
      }
    }
  }

  return total;
};

/**
 * Function: getRowLayout
 *
 * Returns the column positions for the given row and table width.
 */
TableLayout.prototype.getRowLayout = function (row, width) {
  var cells = this.graph.model.getChildCells(row, true);
  var off = this.graph.getActualStartSize(row, true);
  var sw = this.getSize(cells, true);
  var rw = width - off.x - off.width;
  var result = [];
  var x = off.x;

  for (var i = 0; i < cells.length; i++) {
    var cell = this.graph.getCellGeometry(cells[i]);

    if (cell != null) {
      x += (cell.width * rw) / sw;
      result.push(Math.round(x));
    }
  }

  return result;
};

/**
 * Function: layoutRow
 *
 * Places the cells at the given positions in the given row.
 */
TableLayout.prototype.layoutRow = function (row, positions, height, tw) {
  var model = this.graph.getModel();
  var cells = model.getChildCells(row, true);
  var off = this.graph.getActualStartSize(row, true);
  var x = off.x;
  var sw = 0;

  if (positions != null) {
    positions = positions.slice();
    positions.splice(0, 0, off.x);
  }

  for (var i = 0; i < cells.length; i++) {
    var cell = this.graph.getCellGeometry(cells[i]);

    if (cell != null) {
      cell = cell.clone();

      cell.y = off.y;
      cell.height = height - off.y - off.height;

      if (positions != null) {
        cell.x = positions[i];
        cell.width = positions[i + 1] - cell.x;

        // Fills with last cell if not enough cells
        if (i == cells.length - 1 && i < positions.length - 2) {
          cell.width = tw - cell.x - off.x - off.width;
        }
      } else {
        cell.x = x;
        x += cell.width;

        if (i == cells.length - 1) {
          cell.width = tw - off.x - off.width - sw;
        } else {
          sw += cell.width;
        }
      }

      model.setGeometry(cells[i], cell);
    }
  }

  return sw;
};

/**
 * Function: execute
 *
 * Implements <mxGraphLayout.execute>.
 */
TableLayout.prototype.execute = function (parent) {
  if (parent != null) {
    var offset = this.graph.getActualStartSize(parent, true);
    var table = this.graph.getCellGeometry(parent);
    var style = this.graph.getCellStyle(parent);
    var resizeLastRow = mxUtils.getValue(style, 'resizeLastRow', '0') == '1';
    var resizeLast = mxUtils.getValue(style, 'resizeLast', '0') == '1';
    var fixedRows = mxUtils.getValue(style, 'fixedRows', '0') == '1';
    var model = this.graph.getModel();
    var sw = 0;

    model.beginUpdate();
    try {
      var th = table.height - offset.y - offset.height;
      var tw = table.width - offset.x - offset.width;
      var rows = model.getChildCells(parent, true);
      var sh = this.getSize(rows, false);

      if (th > 0 && tw > 0 && rows.length > 0 && sh > 0) {
        if (resizeLastRow) {
          var row = this.graph.getCellGeometry(rows[rows.length - 1]);

          if (row != null) {
            row = row.clone();
            row.height = th - sh + row.height;
            model.setGeometry(rows[rows.length - 1], row);
          }
        }

        var pos = resizeLast ? null : this.getRowLayout(rows[0], tw);
        var y = offset.y;

        // Updates row geometries
        for (var i = 0; i < rows.length; i++) {
          var row = this.graph.getCellGeometry(rows[i]);

          if (row != null) {
            row = row.clone();
            row.x = offset.x;
            row.width = tw;
            row.y = Math.round(y);

            if (resizeLastRow || fixedRows) {
              y += row.height;
            } else {
              y += (row.height / sh) * th;
            }

            row.height = Math.round(y) - row.y;
            model.setGeometry(rows[i], row);
          }

          // Updates cell geometries
          sw = Math.max(sw, this.layoutRow(rows[i], pos, row.height, tw));
        }

        if (fixedRows && th < sh) {
          table = table.clone();
          table.height = y + offset.height;
          model.setGeometry(parent, table);
        }

        if (resizeLast && tw < sw + Graph.minTableColumnWidth) {
          table = table.clone();
          table.width = sw + offset.width + offset.x + Graph.minTableColumnWidth;
          model.setGeometry(parent, table);
        }
      }
    } finally {
      model.endUpdate();
    }
  }
};

(function () {
  /**
   * Reset the list of processed edges.
   */
  var mxGraphViewResetValidationState = mxGraphView.prototype.resetValidationState;
  mxGraphView.prototype.resetValidationState = function () {
    mxGraphViewResetValidationState.apply(this, arguments);

    this.validEdges = [];
  };

  /**
   * Updates jumps for valid edges and repaints if needed.
   */
  var mxGraphViewValidateCellState = mxGraphView.prototype.validateCellState;
  mxGraphView.prototype.validateCellState = function (cell, recurse) {
    recurse = recurse != null ? recurse : true;
    var state = this.getState(cell);

    // Forces repaint if jumps change on a valid edge
    if (
      state != null &&
      recurse &&
      this.graph.model.isEdge(state.cell) &&
      state.style != null &&
      state.style[mxConstants.STYLE_CURVED] != 1 &&
      !state.invalid &&
      this.updateLineJumps(state)
    ) {
      this.graph.cellRenderer.redraw(state, false, this.isRendering());
    }

    state = mxGraphViewValidateCellState.apply(this, arguments);

    // Adds to the list of edges that may intersect with later edges
    if (
      state != null &&
      recurse &&
      this.graph.model.isEdge(state.cell) &&
      state.style != null &&
      state.style[mxConstants.STYLE_CURVED] != 1
    ) {
      // LATER: Reuse jumps for valid edges
      this.validEdges.push(state);
    }

    return state;
  };

  /**
   * Forces repaint if routed points have changed.
   */
  var mxCellRendererIsShapeInvalid = mxCellRenderer.prototype.isShapeInvalid;
  mxCellRenderer.prototype.isShapeInvalid = function (state, shape) {
    return (
      mxCellRendererIsShapeInvalid.apply(this, arguments) ||
      (state.routedPoints != null &&
        shape.routedPoints != null &&
        !mxUtils.equalPoints(shape.routedPoints, state.routedPoints))
    );
  };

  /**
   * Updates jumps for invalid edges.
   */
  var mxGraphViewUpdateCellState = mxGraphView.prototype.updateCellState;
  mxGraphView.prototype.updateCellState = function (state) {
    mxGraphViewUpdateCellState.apply(this, arguments);

    // Updates jumps on invalid edge before repaint
    if (this.graph.model.isEdge(state.cell) && state.style[mxConstants.STYLE_CURVED] != 1) {
      this.updateLineJumps(state);
    }
  };

  /**
   * Updates the jumps between given state and processed edges.
   */
  mxGraphView.prototype.updateLineJumps = function (state) {
    var pts = state.absolutePoints;

    if (Graph.lineJumpsEnabled) {
      var changed = state.routedPoints != null;
      var actual = null;

      if (pts != null && this.validEdges != null && mxUtils.getValue(state.style, 'jumpStyle', 'none') !== 'none') {
        var thresh = 0.5 * this.scale;
        changed = false;
        actual = [];

        // Type 0 means normal waypoint, 1 means jump
        function addPoint(type, x, y) {
          var rpt = new mxPoint(x, y);
          rpt.type = type;

          actual.push(rpt);
          var curr = state.routedPoints != null ? state.routedPoints[actual.length - 1] : null;

          return curr == null || curr.type != type || curr.x != x || curr.y != y;
        }

        for (var i = 0; i < pts.length - 1; i++) {
          var p1 = pts[i + 1];
          var p0 = pts[i];
          var list = [];

          // Ignores waypoints on straight segments
          var pn = pts[i + 2];

          while (
            i < pts.length - 2 &&
            mxUtils.ptSegDistSq(p0.x, p0.y, pn.x, pn.y, p1.x, p1.y) < 1 * this.scale * this.scale
          ) {
            p1 = pn;
            i++;
            pn = pts[i + 2];
          }

          changed = addPoint(0, p0.x, p0.y) || changed;

          // Processes all previous edges
          for (var e = 0; e < this.validEdges.length; e++) {
            var state2 = this.validEdges[e];
            var pts2 = state2.absolutePoints;

            if (pts2 != null && mxUtils.intersects(state, state2) && state2.style['noJump'] != '1') {
              // Compares each segment of the edge with the current segment
              for (var j = 0; j < pts2.length - 1; j++) {
                var p3 = pts2[j + 1];
                var p2 = pts2[j];

                // Ignores waypoints on straight segments
                pn = pts2[j + 2];

                while (
                  j < pts2.length - 2 &&
                  mxUtils.ptSegDistSq(p2.x, p2.y, pn.x, pn.y, p3.x, p3.y) < 1 * this.scale * this.scale
                ) {
                  p3 = pn;
                  j++;
                  pn = pts2[j + 2];
                }

                var pt = mxUtils.intersection(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);

                // Handles intersection between two segments
                if (
                  pt != null &&
                  (Math.abs(pt.x - p0.x) > thresh || Math.abs(pt.y - p0.y) > thresh) &&
                  (Math.abs(pt.x - p1.x) > thresh || Math.abs(pt.y - p1.y) > thresh) &&
                  (Math.abs(pt.x - p2.x) > thresh || Math.abs(pt.y - p2.y) > thresh) &&
                  (Math.abs(pt.x - p3.x) > thresh || Math.abs(pt.y - p3.y) > thresh)
                ) {
                  var dx = pt.x - p0.x;
                  var dy = pt.y - p0.y;
                  var temp = { distSq: dx * dx + dy * dy, x: pt.x, y: pt.y };

                  // Intersections must be ordered by distance from start of segment
                  for (var t = 0; t < list.length; t++) {
                    if (list[t].distSq > temp.distSq) {
                      list.splice(t, 0, temp);
                      temp = null;

                      break;
                    }
                  }

                  // Ignores multiple intersections at segment joint
                  if (
                    temp != null &&
                    (list.length == 0 || list[list.length - 1].x !== temp.x || list[list.length - 1].y !== temp.y)
                  ) {
                    list.push(temp);
                  }
                }
              }
            }
          }

          // Adds ordered intersections to routed points
          for (var j = 0; j < list.length; j++) {
            changed = addPoint(1, list[j].x, list[j].y) || changed;
          }
        }

        var pt = pts[pts.length - 1];
        changed = addPoint(0, pt.x, pt.y) || changed;
      }

      state.routedPoints = actual;

      return changed;
    } else {
      return false;
    }
  };

  /**
   * Overrides painting the actual shape for taking into account jump style.
   */
  var mxConnectorPaintLine = mxConnector.prototype.paintLine;

  mxConnector.prototype.paintLine = function (c, absPts, rounded) {
    // Required for checking dirty state
    this.routedPoints = this.state != null ? this.state.routedPoints : null;

    if (
      this.outline ||
      this.state == null ||
      this.style == null ||
      this.state.routedPoints == null ||
      this.state.routedPoints.length == 0
    ) {
      mxConnectorPaintLine.apply(this, arguments);
    } else {
      var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
      var size = (parseInt(mxUtils.getValue(this.style, 'jumpSize', Graph.defaultJumpSize)) - 2) / 2 + this.strokewidth;
      var style = mxUtils.getValue(this.style, 'jumpStyle', 'none');
      var moveTo = true;
      var last = null;
      var len = null;
      var pts = [];
      var n = null;
      c.begin();

      for (var i = 0; i < this.state.routedPoints.length; i++) {
        var rpt = this.state.routedPoints[i];
        var pt = new mxPoint(rpt.x / this.scale, rpt.y / this.scale);

        // Takes first and last point from passed-in array
        if (i == 0) {
          pt = absPts[0];
        } else if (i == this.state.routedPoints.length - 1) {
          pt = absPts[absPts.length - 1];
        }

        var done = false;

        // Type 1 is an intersection
        if (last != null && rpt.type == 1) {
          // Checks if next/previous points are too close
          var next = this.state.routedPoints[i + 1];
          var dx = next.x / this.scale - pt.x;
          var dy = next.y / this.scale - pt.y;
          var dist = dx * dx + dy * dy;

          if (n == null) {
            n = new mxPoint(pt.x - last.x, pt.y - last.y);
            len = Math.sqrt(n.x * n.x + n.y * n.y);

            if (len > 0) {
              n.x = (n.x * size) / len;
              n.y = (n.y * size) / len;
            } else {
              n = null;
            }
          }

          if (dist > size * size && len > 0) {
            var dx = last.x - pt.x;
            var dy = last.y - pt.y;
            var dist = dx * dx + dy * dy;

            if (dist > size * size) {
              var p0 = new mxPoint(pt.x - n.x, pt.y - n.y);
              var p1 = new mxPoint(pt.x + n.x, pt.y + n.y);
              pts.push(p0);

              this.addPoints(c, pts, rounded, arcSize, false, null, moveTo);

              var f = Math.round(n.x) < 0 || (Math.round(n.x) == 0 && Math.round(n.y) <= 0) ? 1 : -1;
              moveTo = false;

              if (style == 'sharp') {
                c.lineTo(p0.x - n.y * f, p0.y + n.x * f);
                c.lineTo(p1.x - n.y * f, p1.y + n.x * f);
                c.lineTo(p1.x, p1.y);
              } else if (style == 'arc') {
                f *= 1.3;
                c.curveTo(p0.x - n.y * f, p0.y + n.x * f, p1.x - n.y * f, p1.y + n.x * f, p1.x, p1.y);
              } else {
                c.moveTo(p1.x, p1.y);
                moveTo = true;
              }

              pts = [p1];
              done = true;
            }
          }
        } else {
          n = null;
        }

        if (!done) {
          pts.push(pt);
          last = pt;
        }
      }

      this.addPoints(c, pts, rounded, arcSize, false, null, moveTo);
      c.stroke();
    }
  };

  /**
   * Adds support for snapToPoint style.
   */
  var mxGraphViewUpdateFloatingTerminalPoint = mxGraphView.prototype.updateFloatingTerminalPoint;

  mxGraphView.prototype.updateFloatingTerminalPoint = function (edge, start, end, source) {
    if (start != null && edge != null && (start.style['snapToPoint'] == '1' || edge.style['snapToPoint'] == '1')) {
      start = this.getTerminalPort(edge, start, source);
      var next = this.getNextPoint(edge, end, source);

      var orth = this.graph.isOrthogonal(edge);
      var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));
      var center = new mxPoint(start.getCenterX(), start.getCenterY());

      if (alpha != 0) {
        var cos = Math.cos(-alpha);
        var sin = Math.sin(-alpha);
        next = mxUtils.getRotatedPoint(next, cos, sin, center);
      }

      var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
      border += parseFloat(
        edge.style[source ? mxConstants.STYLE_SOURCE_PERIMETER_SPACING : mxConstants.STYLE_TARGET_PERIMETER_SPACING] ||
          0
      );
      var pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border);

      if (alpha != 0) {
        var cos = Math.cos(alpha);
        var sin = Math.sin(alpha);
        pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
      }

      edge.setAbsoluteTerminalPoint(this.snapToAnchorPoint(edge, start, end, source, pt), source);
    } else {
      mxGraphViewUpdateFloatingTerminalPoint.apply(this, arguments);
    }
  };

  mxGraphView.prototype.snapToAnchorPoint = function (edge, start, end, source, pt) {
    if (start != null && edge != null) {
      var constraints = this.graph.getAllConnectionConstraints(start);
      var nearest = null;
      var dist = null;

      if (constraints != null) {
        for (var i = 0; i < constraints.length; i++) {
          var cp = this.graph.getConnectionPoint(start, constraints[i]);

          if (cp != null) {
            var tmp = (cp.x - pt.x) * (cp.x - pt.x) + (cp.y - pt.y) * (cp.y - pt.y);

            if (dist == null || tmp < dist) {
              nearest = cp;
              dist = tmp;
            }
          }
        }
      }

      if (nearest != null) {
        pt = nearest;
      }
    }

    return pt;
  };

  /**
   * Adds support for placeholders in text elements of shapes.
   */
  var mxStencilEvaluateTextAttribute = mxStencil.prototype.evaluateTextAttribute;

  mxStencil.prototype.evaluateTextAttribute = function (node, attribute, shape) {
    var result = mxStencilEvaluateTextAttribute.apply(this, arguments);
    var placeholders = node.getAttribute('placeholders');

    if (placeholders == '1' && shape.state != null) {
      result = shape.state.view.graph.replacePlaceholders(shape.state.cell, result);
    }

    return result;
  };

  /**
   * Adds custom stencils defined via shape=stencil(value) style. The value is a base64 encoded, compressed and
   * URL encoded XML definition of the shape according to the stencil definition language of mxGraph.
   *
   * Needs to be in this file to make sure its part of the embed client code. Also the check for ZLib is
   * different than for the Editor code.
   */
  var mxCellRendererCreateShape = mxCellRenderer.prototype.createShape;
  mxCellRenderer.prototype.createShape = function (state) {
    if (state.style != null && typeof pako !== 'undefined') {
      var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null);

      // Extracts and decodes stencil XML if shape has the form shape=stencil(value)
      if (shape != null && typeof shape === 'string' && shape.substring(0, 8) == 'stencil(') {
        try {
          var stencil = shape.substring(8, shape.length - 1);
          var doc = mxUtils.parseXml(Graph.decompress(stencil));

          return new mxShape(new mxStencil(doc.documentElement));
        } catch (e) {
          if (window.console != null) {
            console.log('Error in shape: ' + e);
          }
        }
      }
    }

    return mxCellRendererCreateShape.apply(this, arguments);
  };
})();

/**
 * Overrides stencil registry for dynamic loading of stencils.
 */
/**
 * Maps from library names to an array of Javascript filenames,
 * which are synchronously loaded. Currently only stencil files
 * (.xml) and JS files (.js) are supported.
 * IMPORTANT: For embedded diagrams to work entries must also
 * be added in EmbedServlet.java.
 */
mxStencilRegistry.libraries = {};

/**
 * Global switch to disable dynamic loading.
 */
mxStencilRegistry.dynamicLoading = true;

/**
 * Global switch to disable eval for JS (preload all JS instead).
 */
mxStencilRegistry.allowEval = true;

/**
 * Stores all package names that have been dynamically loaded.
 * Each package is only loaded once.
 */
mxStencilRegistry.packages = [];

// Extends the default stencil registry to add dynamic loading
mxStencilRegistry.getStencil = function (name) {
  var result = mxStencilRegistry.stencils[name];

  if (result == null && mxCellRenderer.defaultShapes[name] == null && mxStencilRegistry.dynamicLoading) {
    var basename = mxStencilRegistry.getBasenameForStencil(name);

    // Loads stencil files and tries again
    if (basename != null) {
      var libs = mxStencilRegistry.libraries[basename];

      if (libs != null) {
        if (mxStencilRegistry.packages[basename] == null) {
          for (var i = 0; i < libs.length; i++) {
            var fname = libs[i];

            if (fname.toLowerCase().substring(fname.length - 4, fname.length) == '.xml') {
              mxStencilRegistry.loadStencilSet(fname, null);
            } else if (fname.toLowerCase().substring(fname.length - 3, fname.length) == '.js') {
              try {
                if (mxStencilRegistry.allowEval) {
                  var req = mxUtils.load(fname);

                  if (req != null && req.getStatus() >= 200 && req.getStatus() <= 299) {
                    eval.call(window, req.getText());
                  }
                }
              } catch (e) {
                if (window.console != null) {
                  console.log('error in getStencil:', fname, e);
                }
              }
            } else {
              // FIXME: This does not yet work as the loading is triggered after
              // the shape was used in the graph, at which point the keys have
              // typically been translated in the calling method.
              //mxResources.add(fname);
            }
          }

          mxStencilRegistry.packages[basename] = 1;
        }
      } else {
        // Replaces '_-_' with '_'
        basename = basename.replace('_-_', '_');
        mxStencilRegistry.loadStencilSet(STENCIL_PATH + '/' + basename + '.xml', null);
      }

      result = mxStencilRegistry.stencils[name];
    }
  }

  return result;
};

// Returns the basename for the given stencil or null if no file must be
// loaded to render the given stencil.
mxStencilRegistry.getBasenameForStencil = function (name) {
  var tmp = null;

  if (name != null && typeof name === 'string') {
    var parts = name.split('.');

    if (parts.length > 0 && parts[0] == 'mxgraph') {
      tmp = parts[1];

      for (var i = 2; i < parts.length - 1; i++) {
        tmp += '/' + parts[i];
      }
    }
  }

  return tmp;
};

// Loads the given stencil set
mxStencilRegistry.loadStencilSet = function (stencilFile, postStencilLoad, force, async) {
  force = force != null ? force : false;

  // Uses additional cache for detecting previous load attempts
  var xmlDoc = mxStencilRegistry.packages[stencilFile];

  if (force || xmlDoc == null) {
    var install = false;

    if (xmlDoc == null) {
      try {
        if (async) {
          mxStencilRegistry.loadStencil(
            stencilFile,
            mxUtils.bind(this, function (xmlDoc2) {
              if (xmlDoc2 != null && xmlDoc2.documentElement != null) {
                mxStencilRegistry.packages[stencilFile] = xmlDoc2;
                install = true;
                mxStencilRegistry.parseStencilSet(xmlDoc2.documentElement, postStencilLoad, install);
              }
            })
          );

          return;
        } else {
          xmlDoc = mxStencilRegistry.loadStencil(stencilFile);
          mxStencilRegistry.packages[stencilFile] = xmlDoc;
          install = true;
        }
      } catch (e) {
        if (window.console != null) {
          console.log('error in loadStencilSet:', stencilFile, e);
        }
      }
    }

    if (xmlDoc != null && xmlDoc.documentElement != null) {
      mxStencilRegistry.parseStencilSet(xmlDoc.documentElement, postStencilLoad, install);
    }
  }
};

// Loads the given stencil XML file.
mxStencilRegistry.loadStencil = function (filename, fn) {
  if (fn != null) {
    var req = mxUtils.get(
      filename,
      mxUtils.bind(this, function (req) {
        fn(req.getStatus() >= 200 && req.getStatus() <= 299 ? req.getXml() : null);
      })
    );
  } else {
    return mxUtils.load(filename).getXml();
  }
};

// Takes array of strings
mxStencilRegistry.parseStencilSets = function (stencils) {
  for (var i = 0; i < stencils.length; i++) {
    mxStencilRegistry.parseStencilSet(mxUtils.parseXml(stencils[i]).documentElement);
  }
};

// Parses the given stencil set
mxStencilRegistry.parseStencilSet = function (root, postStencilLoad, install) {
  if (root.nodeName == 'stencils') {
    var shapes = root.firstChild;

    while (shapes != null) {
      if (shapes.nodeName == 'shapes') {
        mxStencilRegistry.parseStencilSet(shapes, postStencilLoad, install);
      }

      shapes = shapes.nextSibling;
    }
  } else {
    install = install != null ? install : true;
    var shape = root.firstChild;
    var packageName = '';
    var name = root.getAttribute('name');

    if (name != null) {
      packageName = name + '.';
    }

    while (shape != null) {
      if (shape.nodeType == mxConstants.NODETYPE_ELEMENT) {
        name = shape.getAttribute('name');

        if (name != null) {
          packageName = packageName.toLowerCase();
          var stencilName = name.replace(/ /g, '_');

          if (install) {
            mxStencilRegistry.addStencil(packageName + stencilName.toLowerCase(), new mxStencil(shape));
          }

          if (postStencilLoad != null) {
            var w = shape.getAttribute('w');
            var h = shape.getAttribute('h');

            w = w == null ? 80 : parseInt(w, 10);
            h = h == null ? 80 : parseInt(h, 10);

            postStencilLoad(packageName, stencilName, name, w, h);
          }
        }
      }

      shape = shape.nextSibling;
    }
  }
};

/**
 * These overrides are only added if mxVertexHandler is defined (ie. not in embedded graph)
 */
if (typeof mxVertexHandler != 'undefined') {
  (function () {
    // Sets colors for handles
    mxConstants.HANDLE_FILLCOLOR = '#29b6f2';
    mxConstants.HANDLE_STROKECOLOR = '#0088cf';
    mxConstants.VERTEX_SELECTION_COLOR = '#00a8ff';
    mxConstants.OUTLINE_COLOR = '#00a8ff';
    mxConstants.OUTLINE_HANDLE_FILLCOLOR = '#99ccff';
    mxConstants.OUTLINE_HANDLE_STROKECOLOR = '#00a8ff';
    mxConstants.CONNECT_HANDLE_FILLCOLOR = '#cee7ff';
    mxConstants.EDGE_SELECTION_COLOR = '#00a8ff';
    mxConstants.DEFAULT_VALID_COLOR = '#00a8ff';
    mxConstants.LABEL_HANDLE_FILLCOLOR = '#cee7ff';
    mxConstants.GUIDE_COLOR = '#0088cf';
    mxConstants.HIGHLIGHT_OPACITY = 30;
    mxConstants.HIGHLIGHT_SIZE = 5;

    // Enables snapping to off-grid terminals for edge waypoints
    mxEdgeHandler.prototype.snapToTerminals = true;

    // Enables guides
    mxGraphHandler.prototype.guidesEnabled = true;

    // Removes parents where all child cells are moved out
    mxGraphHandler.prototype.removeEmptyParents = true;

    // Enables fading of rubberband
    mxRubberband.prototype.fadeOut = true;

    // Alt-move disables guides
    mxGuide.prototype.isEnabledForEvent = function (evt) {
      return !mxEvent.isAltDown(evt);
    };

    // Ignores all table cells in layouts
    var graphLayoutIsVertexIgnored = mxGraphLayout.prototype.isVertexIgnored;
    mxGraphLayout.prototype.isVertexIgnored = function (vertex) {
      return (
        graphLayoutIsVertexIgnored.apply(this, arguments) ||
        this.graph.isTableRow(vertex) ||
        this.graph.isTableCell(vertex)
      );
    };

    // Extends connection handler to enable ctrl+drag for cloning source cell
    // since copyOnConnect is now disabled by default
    var mxConnectionHandlerCreateTarget = mxConnectionHandler.prototype.isCreateTarget;
    mxConnectionHandler.prototype.isCreateTarget = function (evt) {
      return this.graph.isCloneEvent(evt) || mxConnectionHandlerCreateTarget.apply(this, arguments);
    };

    // Overrides highlight shape for connection points
    mxConstraintHandler.prototype.createHighlightShape = function () {
      var hl = new mxEllipse(null, this.highlightColor, this.highlightColor, 0);
      hl.opacity = mxConstants.HIGHLIGHT_OPACITY;

      return hl;
    };

    // Overrides edge preview to use current edge shape and default style
    mxConnectionHandler.prototype.livePreview = true;
    mxConnectionHandler.prototype.cursor = 'crosshair';

    // Uses current edge style for connect preview
    mxConnectionHandler.prototype.createEdgeState = function (me) {
      var style = this.graph.createCurrentEdgeStyle();
      var edge = this.graph.createEdge(null, null, null, null, null, style);
      var state = new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));

      for (var key in this.graph.currentEdgeStyle) {
        state.style[key] = this.graph.currentEdgeStyle[key];
      }

      return state;
    };

    // Overrides dashed state with current edge style
    var connectionHandlerCreateShape = mxConnectionHandler.prototype.createShape;
    mxConnectionHandler.prototype.createShape = function () {
      var shape = connectionHandlerCreateShape.apply(this, arguments);

      shape.isDashed = this.graph.currentEdgeStyle[mxConstants.STYLE_DASHED] == '1';

      return shape;
    };

    // Overrides live preview to keep current style
    mxConnectionHandler.prototype.updatePreview = function (valid) {
      // do not change color of preview
    };

    // Overrides connection handler to ignore edges instead of not allowing connections
    var mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
    mxConnectionHandler.prototype.createMarker = function () {
      var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);

      var markerGetCell = marker.getCell;
      marker.getCell = mxUtils.bind(this, function (me) {
        var result = markerGetCell.apply(this, arguments);

        this.error = null;

        return result;
      });

      return marker;
    };

    /**
     *
     */
    Graph.prototype.defaultVertexStyle = {};

    /**
     * Contains the default style for edges.
     */
    Graph.prototype.defaultEdgeStyle = {
      edgeStyle: 'orthogonalEdgeStyle',
      rounded: '0',
      jettySize: 'auto',
      orthogonalLoop: '1',
    };

    /**
     * Returns the current edge style as a string.
     */
    Graph.prototype.createCurrentEdgeStyle = function () {
      var style = 'edgeStyle=' + (this.currentEdgeStyle['edgeStyle'] || 'none') + ';';
      var keys = [
        'shape',
        'curved',
        'rounded',
        'comic',
        'sketch',
        'fillWeight',
        'hachureGap',
        'hachureAngle',
        'jiggle',
        'disableMultiStroke',
        'disableMultiStrokeFill',
        'fillStyle',
        'curveFitting',
        'simplification',
        'comicStyle',
        'jumpStyle',
        'jumpSize',
      ];

      for (var i = 0; i < keys.length; i++) {
        if (this.currentEdgeStyle[keys[i]] != null) {
          style += keys[i] + '=' + this.currentEdgeStyle[keys[i]] + ';';
        }
      }

      // Overrides the global default to match the default edge style
      if (this.currentEdgeStyle['orthogonalLoop'] != null) {
        style += 'orthogonalLoop=' + this.currentEdgeStyle['orthogonalLoop'] + ';';
      } else if (Graph.prototype.defaultEdgeStyle['orthogonalLoop'] != null) {
        style += 'orthogonalLoop=' + Graph.prototype.defaultEdgeStyle['orthogonalLoop'] + ';';
      }

      // Overrides the global default to match the default edge style
      if (this.currentEdgeStyle['jettySize'] != null) {
        style += 'jettySize=' + this.currentEdgeStyle['jettySize'] + ';';
      } else if (Graph.prototype.defaultEdgeStyle['jettySize'] != null) {
        style += 'jettySize=' + Graph.prototype.defaultEdgeStyle['jettySize'] + ';';
      }

      // Special logic for custom property of elbowEdgeStyle
      if (this.currentEdgeStyle['edgeStyle'] == 'elbowEdgeStyle' && this.currentEdgeStyle['elbow'] != null) {
        style += 'elbow=' + this.currentEdgeStyle['elbow'] + ';';
      }

      if (this.currentEdgeStyle['html'] != null) {
        style += 'html=' + this.currentEdgeStyle['html'] + ';';
      } else {
        style += 'html=1;';
      }

      return style;
    };

    /**
     * Removes implicit styles from cell styles so that dark mode works using the
     * default values from the stylesheet.
     */
    Graph.prototype.updateCellStyles = function (key, value, cells) {
      this.model.beginUpdate();
      try {
        for (var i = 0; i < cells.length; i++) {
          if (this.model.isVertex(cells[i]) || this.model.isEdge(cells[i])) {
            this.setCellStyles(key, null, [cells[i]]);
            var style = this.getCellStyle(cells[i]);
            var temp = style[key];

            if (value != (temp == null ? mxConstants.NONE : temp)) {
              this.setCellStyles(key, value, [cells[i]]);
            }
          }
        }
      } finally {
        this.model.endUpdate();
      }
    };

    /**
     * Hook for subclassers.
     */
    Graph.prototype.getPagePadding = function () {
      return new mxPoint(0, 0);
    };

    /**
     * Loads the stylesheet for this graph.
     */
    Graph.prototype.loadStylesheet = function () {
      var node =
        this.themes != null
          ? this.themes[this.defaultThemeName]
          : !mxStyleRegistry.dynamicLoading
          ? null
          : mxUtils.load(STYLE_PATH + '/default.xml').getDocumentElement();

      if (node != null) {
        var dec = new mxCodec(node.ownerDocument);
        dec.decode(node, this.getStylesheet());
      }
    };

    /**
     * Creates lookup from object IDs to cell IDs.
     */
    Graph.prototype.createCellLookup = function (cells, lookup) {
      lookup = lookup != null ? lookup : new Object();

      for (var i = 0; i < cells.length; i++) {
        var cell = cells[i];
        lookup[mxObjectIdentity.get(cell)] = cell.getId();
        var childCount = this.model.getChildCount(cell);

        for (var j = 0; j < childCount; j++) {
          this.createCellLookup([this.model.getChildAt(cell, j)], lookup);
        }
      }

      return lookup;
    };

    /**
     * Creates lookup from original to cloned cell IDs where mapping is
     * the mapping used in cloneCells and lookup is a mapping from
     * object IDs to cell IDs.
     */
    Graph.prototype.createCellMapping = function (mapping, lookup, cellMapping) {
      cellMapping = cellMapping != null ? cellMapping : new Object();

      for (var objectId in mapping) {
        var cellId = lookup[objectId];

        if (cellMapping[cellId] == null) {
          // Uses empty string if clone ID was null which means
          // the cell was cloned but not inserted into the model.
          cellMapping[cellId] = mapping[objectId].getId() || '';
        }
      }

      return cellMapping;
    };

    /**
     *
     */
    Graph.prototype.importGraphModel = function (node, dx, dy, crop) {
      dx = dx != null ? dx : 0;
      dy = dy != null ? dy : 0;

      var codec = new mxCodec(node.ownerDocument);
      var tempModel = new mxGraphModel();
      codec.decode(node, tempModel);
      var cells = [];

      // Clones cells to remove invalid edges
      var cloneMap = new Object();
      var cellMapping = new Object();
      var layers = tempModel.getChildren(this.cloneCell(tempModel.root, this.isCloneInvalidEdges(), cloneMap));

      if (layers != null) {
        // Creates lookup from object IDs to cell IDs
        var lookup = this.createCellLookup([tempModel.root]);

        // Uses copy as layers are removed from array inside loop
        layers = layers.slice();

        this.model.beginUpdate();
        try {
          // Merges into unlocked current layer if one layer is pasted
          if (layers.length == 1 && !this.isCellLocked(this.getDefaultParent())) {
            var children = tempModel.getChildren(layers[0]);

            if (children != null) {
              cells = this.moveCells(children, dx, dy, false, this.getDefaultParent());

              // Imported default parent maps to local default parent
              cellMapping[tempModel.getChildAt(tempModel.root, 0).getId()] = this.getDefaultParent().getId();
            }
          } else {
            for (var i = 0; i < layers.length; i++) {
              var children = this.model.getChildren(
                this.moveCells([layers[i]], dx, dy, false, this.model.getRoot())[0]
              );

              if (children != null) {
                cells = cells.concat(children);
              }
            }
          }

          if (cells != null) {
            // Adds mapping for all cloned entries from imported to local cell ID
            this.createCellMapping(cloneMap, lookup, cellMapping);
            this.updateCustomLinks(cellMapping, cells);

            if (crop) {
              if (this.isGridEnabled()) {
                dx = this.snap(dx);
                dy = this.snap(dy);
              }

              var bounds = this.getBoundingBoxFromGeometry(cells, true);

              if (bounds != null) {
                this.moveCells(cells, dx - bounds.x, dy - bounds.y);
              }
            }
          }
        } finally {
          this.model.endUpdate();
        }
      }

      return cells;
    };

    /**
     * Translates this point by the given vector.
     *
     * @param {number} dx X-coordinate of the translation.
     * @param {number} dy Y-coordinate of the translation.
     */
    Graph.prototype.encodeCells = function (cells) {
      var cloneMap = new Object();
      var clones = this.cloneCells(cells, null, cloneMap);

      // Creates a dictionary for fast lookups
      var dict = new mxDictionary();

      for (var i = 0; i < cells.length; i++) {
        dict.put(cells[i], true);
      }

      var codec = new mxCodec();
      var model = new mxGraphModel();
      var parent = model.getChildAt(model.getRoot(), 0);

      for (var i = 0; i < clones.length; i++) {
        model.add(parent, clones[i]);

        // Checks for orphaned relative children and makes absolute
        var state = this.view.getState(cells[i]);

        if (state != null) {
          var geo = this.getCellGeometry(clones[i]);

          if (
            geo != null &&
            geo.relative &&
            !this.model.isEdge(cells[i]) &&
            dict.get(this.model.getParent(cells[i])) == null
          ) {
            geo.offset = null;
            geo.relative = false;
            geo.x = state.x / state.view.scale - state.view.translate.x;
            geo.y = state.y / state.view.scale - state.view.translate.y;
          }
        }
      }

      this.updateCustomLinks(this.createCellMapping(cloneMap, this.createCellLookup(cells)), clones);

      return codec.encode(model);
    };

    /**
     * Overridden to check for table shape.
     */
    Graph.prototype.isSwimlane = function (cell, ignoreState) {
      if (cell != null && this.model.getParent(cell) != this.model.getRoot() && !this.model.isEdge(cell)) {
        var shape = this.getCurrentCellStyle(cell, ignoreState)[mxConstants.STYLE_SHAPE];

        return shape == mxConstants.SHAPE_SWIMLANE || shape == 'table';
      }

      return false;
    };

    /**
     * Overridden to add expand style.
     */
    var graphIsExtendParent = Graph.prototype.isExtendParent;
    Graph.prototype.isExtendParent = function (cell) {
      var parent = this.model.getParent(cell);

      if (parent != null) {
        var style = this.getCurrentCellStyle(parent);

        if (style['expand'] != null) {
          return style['expand'] != '0';
        }
      }

      return graphIsExtendParent.apply(this, arguments) && (parent == null || !this.isTable(parent));
    };

    /**
     * Overridden to use table cell instead of table as parent.
     */
    var graphSplitEdge = Graph.prototype.splitEdge;
    Graph.prototype.splitEdge = function (edge, cells, newEdge, dx, dy, x, y, parent) {
      if (parent == null) {
        parent = this.model.getParent(edge);

        if (this.isTable(parent) || this.isTableRow(parent)) {
          parent = this.getCellAt(x, y, null, true, false);
        }
      }

      graphSplitEdge.apply(this, [edge, cells, newEdge, dx, dy, x, y, parent]);
    };

    /**
     * Overridden to flatten cell hierarchy for selecting next and previous.
     */
    var graphSelectCell = Graph.prototype.selectCell;
    Graph.prototype.selectCell = function (isNext, isParent, isChild) {
      if (isParent || isChild) {
        graphSelectCell.apply(this, arguments);
      } else {
        var cell = this.getSelectionCell();
        var index = null;
        var cells = [];

        // LATER: Reverse traverse order for !isNext
        var flatten = mxUtils.bind(this, function (temp) {
          if (this.view.getState(temp) != null && (this.model.isVertex(temp) || this.model.isEdge(temp))) {
            cells.push(temp);

            if (temp == cell) {
              index = cells.length - 1;
            } else if (
              (isNext && cell == null && cells.length > 0) ||
              (index != null && isNext && cells.length > index) ||
              (!isNext && index > 0)
            ) {
              return;
            }
          }

          for (var i = 0; i < this.model.getChildCount(temp); i++) {
            flatten(this.model.getChildAt(temp, i));
          }
        });

        flatten(this.model.root);

        if (cells.length > 0) {
          if (index != null) {
            index = mxUtils.mod(index + (isNext ? 1 : -1), cells.length);
          } else {
            index = 0;
          }

          this.setSelectionCell(cells[index]);
        }
      }
    };

    /**
     * Overrides cloning cells in moveCells.
     */
    var graphMoveCells = Graph.prototype.moveCells;
    Graph.prototype.moveCells = function (cells, dx, dy, clone, target, evt, mapping) {
      mapping = mapping != null ? mapping : new Object();

      // Replaces source tables with rows
      if (this.isTable(target)) {
        var newCells = [];

        for (var i = 0; i < cells.length; i++) {
          if (this.isTable(cells[i])) {
            newCells = newCells.concat(this.model.getChildCells(cells[i], true).reverse());
          } else {
            newCells.push(cells[i]);
          }
        }

        cells = newCells;
      }

      this.model.beginUpdate();
      try {
        // Updates source and target table heights and matches
        // column count for moving rows between tables
        var sourceTables = [];

        for (var i = 0; i < cells.length; i++) {
          if (target != null && this.isTableRow(cells[i])) {
            var parent = this.model.getParent(cells[i]);
            var row = this.getCellGeometry(cells[i]);

            if (this.isTable(parent)) {
              sourceTables.push(parent);
            }

            if (
              parent != null &&
              row != null &&
              this.isTable(parent) &&
              this.isTable(target) &&
              (clone || parent != target)
            ) {
              if (!clone) {
                var table = this.getCellGeometry(parent);

                if (table != null) {
                  table = table.clone();
                  table.height -= row.height;
                  this.model.setGeometry(parent, table);
                }
              }

              var table = this.getCellGeometry(target);

              if (table != null) {
                table = table.clone();
                table.height += row.height;
                this.model.setGeometry(target, table);
              }

              // Matches column count
              var rows = this.model.getChildCells(target, true);

              if (rows.length > 0) {
                var cell = clone ? this.cloneCell(cells[i]) : cells[i];

                var sourceCols = this.model.getChildCells(cell, true);
                var cols = this.model.getChildCells(rows[0], true);
                var count = cols.length - sourceCols.length;

                if (count > 0) {
                  for (var j = 0; j < count; j++) {
                    var col = this.cloneCell(sourceCols[sourceCols.length - 1]);

                    if (col != null) {
                      col.value = '';

                      this.model.add(cell, col);
                    }
                  }
                } else if (count < 0) {
                  for (var j = 0; j > count; j--) {
                    this.model.remove(sourceCols[sourceCols.length + j - 1]);
                  }
                }

                // Updates column widths
                sourceCols = this.model.getChildCells(cell, true);

                for (var j = 0; j < cols.length; j++) {
                  var geo = this.getCellGeometry(cols[j]);
                  var geo2 = this.getCellGeometry(sourceCols[j]);

                  if (geo != null && geo2 != null) {
                    geo2 = geo2.clone();
                    geo2.width = geo.width;

                    this.model.setGeometry(sourceCols[j], geo2);
                  }
                }
              }
            }
          }
        }

        var result = graphMoveCells.apply(this, arguments);

        // Removes empty tables
        for (var i = 0; i < sourceTables.length; i++) {
          if (!clone && this.model.contains(sourceTables[i]) && this.model.getChildCount(sourceTables[i]) == 0) {
            this.model.remove(sourceTables[i]);
          }
        }

        if (clone) {
          this.updateCustomLinks(this.createCellMapping(mapping, this.createCellLookup(cells)), result);
        }
      } finally {
        this.model.endUpdate();
      }

      return result;
    };

    /**
     * Overriddes to delete label for table cells.
     */
    var graphRemoveCells = Graph.prototype.removeCells;
    Graph.prototype.removeCells = function (cells, includeEdges) {
      var result = [];

      this.model.beginUpdate();
      try {
        // Clears labels on table cells
        for (var i = 0; i < cells.length; i++) {
          if (this.isTableCell(cells[i])) {
            var row = this.model.getParent(cells[i]);
            var table = this.model.getParent(row);

            // Removes table if one cell in one row left
            if (this.model.getChildCount(row) == 1 && this.model.getChildCount(table) == 1) {
              if (mxUtils.indexOf(cells, table) < 0 && mxUtils.indexOf(result, table) < 0) {
                result.push(table);
              }
            } else {
              this.labelChanged(cells[i], '');
            }
          } else {
            // Deletes table if all rows are removed
            if (this.isTableRow(cells[i])) {
              var table = this.model.getParent(cells[i]);

              if (mxUtils.indexOf(cells, table) < 0 && mxUtils.indexOf(result, table) < 0) {
                var rows = this.model.getChildCells(table, true);
                var deleteCount = 0;

                for (var j = 0; j < rows.length; j++) {
                  if (mxUtils.indexOf(cells, rows[j]) >= 0) {
                    deleteCount++;
                  }
                }

                if (deleteCount == rows.length) {
                  result.push(table);
                }
              }
            }

            result.push(cells[i]);
          }
        }

        result = graphRemoveCells.apply(this, [result, includeEdges]);
      } finally {
        this.model.endUpdate();
      }

      return result;
    };

    /**
     * Updates cells IDs for custom links in the given cells.
     */
    Graph.prototype.updateCustomLinks = function (mapping, cells) {
      for (var i = 0; i < cells.length; i++) {
        if (cells[i] != null) {
          this.updateCustomLinksForCell(mapping, cells[i]);
        }
      }
    };

    /**
     * Updates cell IDs in custom links on the given cell and its label.
     */
    Graph.prototype.updateCustomLinksForCell = function (mapping, cell) {
      // Hook for subclassers
    };

    /**
     * Overrides method to provide connection constraints for shapes.
     */
    Graph.prototype.getAllConnectionConstraints = function (terminal, source) {
      if (terminal != null) {
        var constraints = mxUtils.getValue(terminal.style, 'points', null);

        if (constraints != null) {
          // Requires an array of arrays with x, y (0..1), an optional
          // [perimeter (0 or 1), dx, and dy] eg. points=[[0,0,1,-10,10],[0,1,0],[1,1]]
          var result = [];

          try {
            var c = JSON.parse(constraints);

            for (var i = 0; i < c.length; i++) {
              var tmp = c[i];
              result.push(
                new mxConnectionConstraint(
                  new mxPoint(tmp[0], tmp[1]),
                  tmp.length > 2 ? tmp[2] != '0' : true,
                  null,
                  tmp.length > 3 ? tmp[3] : 0,
                  tmp.length > 4 ? tmp[4] : 0
                )
              );
            }
          } catch (e) {
            // ignore
          }

          return result;
        } else if (terminal.shape != null && terminal.shape.bounds != null) {
          var dir = terminal.shape.direction;
          var bounds = terminal.shape.bounds;
          var scale = terminal.shape.scale;
          var w = bounds.width / scale;
          var h = bounds.height / scale;

          if (dir == mxConstants.DIRECTION_NORTH || dir == mxConstants.DIRECTION_SOUTH) {
            var tmp = w;
            w = h;
            h = tmp;
          }

          constraints = terminal.shape.getConstraints(terminal.style, w, h);

          if (constraints != null) {
            return constraints;
          } else if (terminal.shape.stencil != null && terminal.shape.stencil.constraints != null) {
            return terminal.shape.stencil.constraints;
          } else if (terminal.shape.constraints != null) {
            return terminal.shape.constraints;
          }
        }
      }

      return null;
    };

    /**
     * Inverts the elbow edge style without removing existing styles.
     */
    Graph.prototype.flipEdge = function (edge) {
      if (edge != null) {
        var style = this.getCurrentCellStyle(edge);
        var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW, mxConstants.ELBOW_HORIZONTAL);
        var value = elbow == mxConstants.ELBOW_HORIZONTAL ? mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
        this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
      }
    };

    /**
     * Disables drill-down for non-swimlanes.
     */
    Graph.prototype.isValidRoot = function (cell) {
      // Counts non-relative children
      var childCount = this.model.getChildCount(cell);
      var realChildCount = 0;

      for (var i = 0; i < childCount; i++) {
        var child = this.model.getChildAt(cell, i);

        if (this.model.isVertex(child)) {
          var geometry = this.getCellGeometry(child);

          if (geometry != null && !geometry.relative) {
            realChildCount++;
          }
        }
      }

      return realChildCount > 0 || this.isContainer(cell);
    };

    /**
     * Disables drill-down for non-swimlanes.
     */
    Graph.prototype.isValidDropTarget = function (cell, cells, evt) {
      var style = this.getCurrentCellStyle(cell);
      var tables = true;
      var rows = true;

      for (var i = 0; i < cells.length && rows; i++) {
        tables = tables && this.isTable(cells[i]);
        rows = rows && this.isTableRow(cells[i]);
      }

      return (
        (mxUtils.getValue(style, 'part', '0') != '1' || this.isContainer(cell)) &&
        mxUtils.getValue(style, 'dropTarget', '1') != '0' &&
        (mxGraph.prototype.isValidDropTarget.apply(this, arguments) || this.isContainer(cell)) &&
        !this.isTableRow(cell) &&
        (!this.isTable(cell) || rows || tables)
      );
    };

    /**
     * Overrides createGroupCell to set the group style for new groups to 'group'.
     */
    Graph.prototype.createGroupCell = function () {
      var group = mxGraph.prototype.createGroupCell.apply(this, arguments);
      group.setStyle('group');

      return group;
    };

    /**
     * Disables extending parents with stack layouts on add
     */
    Graph.prototype.isExtendParentsOnAdd = function (cell) {
      var result = mxGraph.prototype.isExtendParentsOnAdd.apply(this, arguments);

      if (result && cell != null && this.layoutManager != null) {
        var parent = this.model.getParent(cell);

        if (parent != null) {
          var layout = this.layoutManager.getLayout(parent);

          if (layout != null && layout.constructor == mxStackLayout) {
            result = false;
          }
        }
      }

      return result;
    };

    /**
     * Overrides autosize to add a border.
     */
    Graph.prototype.getPreferredSizeForCell = function (cell) {
      var result = mxGraph.prototype.getPreferredSizeForCell.apply(this, arguments);

      // Adds buffer
      if (result != null) {
        result.width += 10;
        result.height += 4;

        if (this.gridEnabled) {
          result.width = this.snap(result.width);
          result.height = this.snap(result.height);
        }
      }

      return result;
    };

    /**
     * Turns the given cells and returns the changed cells.
     */
    Graph.prototype.turnShapes = function (cells, backwards) {
      var model = this.getModel();
      var select = [];

      model.beginUpdate();
      try {
        for (var i = 0; i < cells.length; i++) {
          var cell = cells[i];

          if (model.isEdge(cell)) {
            var src = model.getTerminal(cell, true);
            var trg = model.getTerminal(cell, false);

            model.setTerminal(cell, trg, true);
            model.setTerminal(cell, src, false);

            var geo = model.getGeometry(cell);

            if (geo != null) {
              geo = geo.clone();

              if (geo.points != null) {
                geo.points.reverse();
              }

              var sp = geo.getTerminalPoint(true);
              var tp = geo.getTerminalPoint(false);

              geo.setTerminalPoint(sp, false);
              geo.setTerminalPoint(tp, true);
              model.setGeometry(cell, geo);

              // Inverts constraints
              var edgeState = this.view.getState(cell);
              var sourceState = this.view.getState(src);
              var targetState = this.view.getState(trg);

              if (edgeState != null) {
                var sc = sourceState != null ? this.getConnectionConstraint(edgeState, sourceState, true) : null;
                var tc = targetState != null ? this.getConnectionConstraint(edgeState, targetState, false) : null;

                this.setConnectionConstraint(cell, src, true, tc);
                this.setConnectionConstraint(cell, trg, false, sc);
              }

              select.push(cell);
            }
          } else if (model.isVertex(cell)) {
            var geo = this.getCellGeometry(cell);

            if (geo != null) {
              // Rotates the size and position in the geometry
              if (!this.isTable(cell) && !this.isTableRow(cell) && !this.isTableCell(cell) && !this.isSwimlane(cell)) {
                geo = geo.clone();
                geo.x += geo.width / 2 - geo.height / 2;
                geo.y += geo.height / 2 - geo.width / 2;
                var tmp = geo.width;
                geo.width = geo.height;
                geo.height = tmp;
                model.setGeometry(cell, geo);
              }

              // Reads the current direction and advances by 90 degrees
              var state = this.view.getState(cell);

              if (state != null) {
                var dirs = [
                  mxConstants.DIRECTION_EAST,
                  mxConstants.DIRECTION_SOUTH,
                  mxConstants.DIRECTION_WEST,
                  mxConstants.DIRECTION_NORTH,
                ];
                var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST);
                this.setCellStyles(
                  mxConstants.STYLE_DIRECTION,
                  dirs[mxUtils.mod(mxUtils.indexOf(dirs, dir) + (backwards ? -1 : 1), dirs.length)],
                  [cell]
                );
              }

              select.push(cell);
            }
          }
        }
      } finally {
        model.endUpdate();
      }

      return select;
    };

    /**
     * Returns true if the given stencil contains any placeholder text.
     */
    Graph.prototype.stencilHasPlaceholders = function (stencil) {
      if (stencil != null && stencil.fgNode != null) {
        var node = stencil.fgNode.firstChild;

        while (node != null) {
          if (node.nodeName == 'text' && node.getAttribute('placeholders') == '1') {
            return true;
          }

          node = node.nextSibling;
        }
      }

      return false;
    };

    /**
     * Updates the child cells with placeholders if metadata of a
     * cell has changed and propagates geometry changes in tables.
     */
    var graphProcessChange = Graph.prototype.processChange;
    Graph.prototype.processChange = function (change) {
      if (
        change instanceof mxGeometryChange &&
        (this.isTableCell(change.cell) || this.isTableRow(change.cell)) &&
        ((change.previous == null && change.geometry != null) ||
          (change.previous != null && !change.previous.equals(change.geometry)))
      ) {
        var cell = change.cell;

        if (this.isTableCell(cell)) {
          cell = this.model.getParent(cell);
        }

        if (this.isTableRow(cell)) {
          cell = this.model.getParent(cell);
        }

        // Forces repaint of table with unchanged style and geometry
        var state = this.view.getState(cell);

        if (state != null && state.shape != null) {
          this.view.invalidate(cell);
          state.shape.bounds = null;
        }
      }

      graphProcessChange.apply(this, arguments);

      if (
        change instanceof mxValueChange &&
        change.cell != null &&
        change.cell.value != null &&
        typeof change.cell.value == 'object'
      ) {
        this.invalidateDescendantsWithPlaceholders(change.cell);
      }
    };

    /**
     * Replaces the given element with a span.
     */
    Graph.prototype.invalidateDescendantsWithPlaceholders = function (cell) {
      // Invalidates all descendants with placeholders
      var desc = this.model.getDescendants(cell);

      // LATER: Check if only label or tooltip have changed
      if (desc.length > 0) {
        for (var i = 0; i < desc.length; i++) {
          var state = this.view.getState(desc[i]);

          if (
            state != null &&
            state.shape != null &&
            state.shape.stencil != null &&
            this.stencilHasPlaceholders(state.shape.stencil)
          ) {
            this.removeStateForCell(desc[i]);
          } else if (this.isReplacePlaceholders(desc[i])) {
            this.view.invalidate(desc[i], false, false);
          }
        }
      }
    };

    /**
     * Replaces the given element with a span.
     */
    Graph.prototype.replaceElement = function (elt, tagName) {
      var span = elt.ownerDocument.createElement(tagName != null ? tagName : 'span');
      var attributes = Array.prototype.slice.call(elt.attributes);

      while ((attr = attributes.pop())) {
        span.setAttribute(attr.nodeName, attr.nodeValue);
      }

      span.innerHTML = elt.innerHTML;
      elt.parentNode.replaceChild(span, elt);
    };

    /**
     *
     */
    Graph.prototype.processElements = function (elt, fn) {
      if (elt != null) {
        var elts = elt.getElementsByTagName('*');

        for (var i = 0; i < elts.length; i++) {
          fn(elts[i]);
        }
      }
    };

    /**
     * Handles label changes for XML user objects.
     */
    Graph.prototype.updateLabelElements = function (cells, fn, tagName) {
      cells = cells != null ? cells : this.getSelectionCells();
      var div = document.createElement('div');

      for (var i = 0; i < cells.length; i++) {
        // Changes font tags inside HTML labels
        if (this.isHtmlLabel(cells[i])) {
          var label = this.convertValueToString(cells[i]);

          if (label != null && label.length > 0) {
            div.innerHTML = label;
            var elts = div.getElementsByTagName(tagName != null ? tagName : '*');

            for (var j = 0; j < elts.length; j++) {
              fn(elts[j]);
            }

            if (div.innerHTML != label) {
              this.cellLabelChanged(cells[i], div.innerHTML);
            }
          }
        }
      }
    };

    /**
     * Handles label changes for XML user objects.
     */
    Graph.prototype.cellLabelChanged = function (cell, value, autoSize) {
      // Removes all illegal control characters in user input
      value = Graph.zapGremlins(value);

      this.model.beginUpdate();
      try {
        if (cell.value != null && typeof cell.value == 'object') {
          if (this.isReplacePlaceholders(cell) && cell.getAttribute('placeholder') != null) {
            // LATER: Handle delete, name change
            var name = cell.getAttribute('placeholder');
            var current = cell;

            while (current != null) {
              if (
                current == this.model.getRoot() ||
                (current.value != null && typeof current.value == 'object' && current.hasAttribute(name))
              ) {
                this.setAttributeForCell(current, name, value);

                break;
              }

              current = this.model.getParent(current);
            }
          }

          var tmp = cell.value.cloneNode(true);

          if (
            Graph.translateDiagram &&
            Graph.diagramLanguage != null &&
            tmp.hasAttribute('label_' + Graph.diagramLanguage)
          ) {
            tmp.setAttribute('label_' + Graph.diagramLanguage, value);
          } else {
            tmp.setAttribute('label', value);
          }

          value = tmp;
        }

        mxGraph.prototype.cellLabelChanged.apply(this, arguments);
      } finally {
        this.model.endUpdate();
      }
    };

    /**
     * Removes transparent empty groups if all children are removed.
     */
    Graph.prototype.cellsRemoved = function (cells) {
      if (cells != null) {
        var dict = new mxDictionary();

        for (var i = 0; i < cells.length; i++) {
          dict.put(cells[i], true);
        }

        // LATER: Recurse up the cell hierarchy
        var parents = [];

        for (var i = 0; i < cells.length; i++) {
          var parent = this.model.getParent(cells[i]);

          if (parent != null && !dict.get(parent)) {
            dict.put(parent, true);
            parents.push(parent);
          }
        }

        for (var i = 0; i < parents.length; i++) {
          var state = this.view.getState(parents[i]);

          if (
            state != null &&
            (this.model.isEdge(state.cell) || this.model.isVertex(state.cell)) &&
            this.isCellDeletable(state.cell) &&
            this.isTransparentState(state)
          ) {
            var allChildren = true;

            for (var j = 0; j < this.model.getChildCount(state.cell) && allChildren; j++) {
              if (!dict.get(this.model.getChildAt(state.cell, j))) {
                allChildren = false;
              }
            }

            if (allChildren) {
              cells.push(state.cell);
            }
          }
        }
      }

      mxGraph.prototype.cellsRemoved.apply(this, arguments);
    };

    /**
     * Overrides ungroup to check if group should be removed.
     */
    Graph.prototype.removeCellsAfterUngroup = function (cells) {
      var cellsToRemove = [];

      for (var i = 0; i < cells.length; i++) {
        if (this.isCellDeletable(cells[i]) && this.isTransparentState(this.view.getState(cells[i]))) {
          cellsToRemove.push(cells[i]);
        }
      }

      cells = cellsToRemove;

      mxGraph.prototype.removeCellsAfterUngroup.apply(this, arguments);
    };

    /**
     * Sets the link for the given cell.
     */
    Graph.prototype.setLinkForCell = function (cell, link) {
      this.setAttributeForCell(cell, 'link', link);
    };

    /**
     * Sets the link for the given cell.
     */
    Graph.prototype.setTooltipForCell = function (cell, link) {
      var key = 'tooltip';

      if (
        Graph.translateDiagram &&
        Graph.diagramLanguage != null &&
        mxUtils.isNode(cell.value) &&
        cell.value.hasAttribute('tooltip_' + Graph.diagramLanguage)
      ) {
        key = 'tooltip_' + Graph.diagramLanguage;
      }

      this.setAttributeForCell(cell, key, link);
    };

    /**
     * Returns the cells in the model (or given array) that have all of the
     * given tags in their tags property.
     */
    Graph.prototype.getAttributeForCell = function (cell, attributeName, defaultValue) {
      var value = cell.value != null && typeof cell.value === 'object' ? cell.value.getAttribute(attributeName) : null;

      return value != null ? value : defaultValue;
    };

    /**
     * Sets the link for the given cell.
     */
    Graph.prototype.setAttributeForCell = function (cell, attributeName, attributeValue) {
      var value = null;

      if (cell.value != null && typeof cell.value == 'object') {
        value = cell.value.cloneNode(true);
      } else {
        var doc = mxUtils.createXmlDocument();

        value = doc.createElement('UserObject');
        value.setAttribute('label', cell.value || '');
      }

      if (attributeValue != null) {
        value.setAttribute(attributeName, attributeValue);
      } else {
        value.removeAttribute(attributeName);
      }

      this.model.setValue(cell, value);
    };

    /**
     * Overridden to stop moving edge labels between cells.
     */
    var graphGetDropTarget = Graph.prototype.getDropTarget;
    Graph.prototype.getDropTarget = function (cells, evt, cell, clone) {
      var model = this.getModel();

      // Disables drop into group if alt is pressed
      if (mxEvent.isAltDown(evt)) {
        return null;
      }

      // Disables dragging edge labels out of edges
      for (var i = 0; i < cells.length; i++) {
        if (this.model.isEdge(this.model.getParent(cells[i]))) {
          return null;
        }
      }

      var target = graphGetDropTarget.apply(this, arguments);

      // Always drops rows to tables
      var rows = true;

      for (var i = 0; i < cells.length && rows; i++) {
        rows = rows && this.isTableRow(cells[i]);
      }

      if (rows) {
        if (this.isTableCell(target)) {
          target = this.model.getParent(target);
        }

        if (this.isTableRow(target)) {
          target = this.model.getParent(target);
        }

        if (!this.isTable(target)) {
          target = null;
        }
      }

      return target;
    };

    /**
     * Overrides double click handling to avoid accidental inserts of new labels in dblClick below.
     */
    Graph.prototype.click = function (me) {
      mxGraph.prototype.click.call(this, me);

      // Stores state and source for checking in dblClick
      this.firstClickState = me.getState();
      this.firstClickSource = me.getSource();
    };

    /**
     * Overrides double click handling to add the tolerance and inserting text.
     */
    Graph.prototype.dblClick = function (evt, cell) {
      if (this.isEnabled()) {
        cell = this.insertTextForEvent(evt, cell);
        mxGraph.prototype.dblClick.call(this, evt, cell);
      }
    };

    /**
     * Overrides double click handling to add the tolerance and inserting text.
     */
    Graph.prototype.insertTextForEvent = function (evt, cell) {
      var pt = mxUtils.convertPoint(this.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));

      // Automatically adds new child cells to edges on double click
      if (evt != null && !this.model.isVertex(cell)) {
        var state = this.model.isEdge(cell) ? this.view.getState(cell) : null;
        var src = mxEvent.getSource(evt);

        if (
          this.firstClickState == state &&
          this.firstClickSource == src &&
          (state == null ||
            state.text == null ||
            state.text.node == null ||
            state.text.boundingBox == null ||
            (!mxUtils.contains(state.text.boundingBox, pt.x, pt.y) &&
              !mxUtils.isAncestorNode(state.text.node, mxEvent.getSource(evt)))) &&
          ((state == null && !this.isCellLocked(this.getDefaultParent())) ||
            (state != null && !this.isCellLocked(state.cell))) &&
          (state != null ||
            (mxClient.IS_VML && src == this.view.getCanvas()) ||
            (mxClient.IS_SVG && src == this.view.getCanvas().ownerSVGElement))
        ) {
          if (state == null) {
            state = this.view.getState(this.getCellAt(pt.x, pt.y));
          }

          cell = this.addText(pt.x, pt.y, state);
        }
      }

      return cell;
    };

    /**
     * Returns a point that specifies the location for inserting cells.
     */
    Graph.prototype.getInsertPoint = function () {
      var gs = this.getGridSize();
      var dx = this.container.scrollLeft / this.view.scale - this.view.translate.x;
      var dy = this.container.scrollTop / this.view.scale - this.view.translate.y;

      if (this.pageVisible) {
        var layout = this.getPageLayout();
        var page = this.getPageSize();
        dx = Math.max(dx, layout.x * page.width);
        dy = Math.max(dy, layout.y * page.height);
      }

      return new mxPoint(this.snap(dx + gs), this.snap(dy + gs));
    };

    /**
     *
     */
    Graph.prototype.getFreeInsertPoint = function () {
      var view = this.view;
      var bds = this.getGraphBounds();
      var pt = this.getInsertPoint();

      // Places at same x-coord and 2 grid sizes below existing graph
      var x = this.snap(
        Math.round(Math.max(pt.x, bds.x / view.scale - view.translate.x + (bds.width == 0 ? 2 * this.gridSize : 0)))
      );
      var y = this.snap(
        Math.round(Math.max(pt.y, (bds.y + bds.height) / view.scale - view.translate.y + 2 * this.gridSize))
      );

      return new mxPoint(x, y);
    };

    /**
     *
     */
    Graph.prototype.getCenterInsertPoint = function (bbox) {
      bbox = bbox != null ? bbox : new mxRectangle();

      if (mxUtils.hasScrollbars(this.container)) {
        return new mxPoint(
          this.snap(
            Math.round(
              (this.container.scrollLeft + this.container.clientWidth / 2) / this.view.scale -
                this.view.translate.x -
                bbox.width / 2
            )
          ),
          this.snap(
            Math.round(
              (this.container.scrollTop + this.container.clientHeight / 2) / this.view.scale -
                this.view.translate.y -
                bbox.height / 2
            )
          )
        );
      } else {
        return new mxPoint(
          this.snap(
            Math.round(this.container.clientWidth / 2 / this.view.scale - this.view.translate.x - bbox.width / 2)
          ),
          this.snap(
            Math.round(this.container.clientHeight / 2 / this.view.scale - this.view.translate.y - bbox.height / 2)
          )
        );
      }
    };

    /**
     * Hook for subclassers to return true if the current insert point was defined
     * using a mouse hover event.
     */
    Graph.prototype.isMouseInsertPoint = function () {
      return false;
    };

    /**
     * Adds a new label at the given position and returns the new cell. State is
     * an optional edge state to be used as the parent for the label. Vertices
     * are not allowed currently as states.
     */
    Graph.prototype.addText = function (x, y, state) {
      // Creates a new edge label with a predefined text
      var label = new mxCell();
      label.value = 'Text';
      label.geometry = new mxGeometry(0, 0, 0, 0);
      label.vertex = true;
      var style = 'html=1;align=center;verticalAlign=middle;resizable=0;points=[];';

      if (state != null && this.model.isEdge(state.cell)) {
        label.style = 'edgeLabel;' + style;
        label.geometry.relative = true;
        label.connectable = false;

        // Resets the relative location stored inside the geometry
        var pt2 = this.view.getRelativePoint(state, x, y);
        label.geometry.x = Math.round(pt2.x * 10000) / 10000;
        label.geometry.y = Math.round(pt2.y);

        // Resets the offset inside the geometry to find the offset from the resulting point
        label.geometry.offset = new mxPoint(0, 0);
        pt2 = this.view.getPoint(state, label.geometry);

        var scale = this.view.scale;
        label.geometry.offset = new mxPoint(Math.round((x - pt2.x) / scale), Math.round((y - pt2.y) / scale));
      } else {
        var tr = this.view.translate;
        label.style = 'text;' + style;
        label.geometry.width = 40;
        label.geometry.height = 20;
        label.geometry.x = Math.round(x / this.view.scale) - tr.x - (state != null ? state.origin.x : 0);
        label.geometry.y = Math.round(y / this.view.scale) - tr.y - (state != null ? state.origin.y : 0);
        label.style += 'autosize=1;';
      }

      this.getModel().beginUpdate();
      try {
        this.addCells([label], state != null ? state.cell : null);
        this.fireEvent(new mxEventObject('textInserted', 'cells', [label]));

        // Updates size of text after possible change of style via event
        this.autoSizeCell(label);
      } finally {
        this.getModel().endUpdate();
      }

      return label;
    };

    /**
     * Adds a handler for clicking on shapes with links. This replaces all links in labels.
     */
    Graph.prototype.addClickHandler = function (highlight, beforeClick, onClick) {
      // Replaces links in labels for consistent right-clicks
      var checkLinks = mxUtils.bind(this, function () {
        var links = this.container.getElementsByTagName('a');

        if (links != null) {
          for (var i = 0; i < links.length; i++) {
            var href = this.getAbsoluteUrl(links[i].getAttribute('href'));

            if (href != null) {
              links[i].setAttribute('rel', this.linkRelation);
              links[i].setAttribute('href', href);

              if (beforeClick != null) {
                mxEvent.addGestureListeners(links[i], null, null, beforeClick);
              }
            }
          }
        }
      });

      this.model.addListener(mxEvent.CHANGE, checkLinks);
      checkLinks();

      var cursor = this.container.style.cursor;
      var tol = this.getTolerance();
      var graph = this;

      var mouseListener = {
        currentState: null,
        currentLink: null,
        highlight:
          highlight != null && highlight != '' && highlight != mxConstants.NONE
            ? new mxCellHighlight(graph, highlight, 4)
            : null,
        startX: 0,
        startY: 0,
        scrollLeft: 0,
        scrollTop: 0,
        updateCurrentState: function (me) {
          var tmp = me.sourceState;

          // Gets topmost intersecting cell with link
          if (tmp == null || graph.getLinkForCell(tmp.cell) == null) {
            var cell = graph.getCellAt(me.getGraphX(), me.getGraphY(), null, null, null, function (state, x, y) {
              return graph.getLinkForCell(state.cell) == null;
            });

            tmp = graph.view.getState(cell);
          }

          if (tmp != this.currentState) {
            if (this.currentState != null) {
              this.clear();
            }

            this.currentState = tmp;

            if (this.currentState != null) {
              this.activate(this.currentState);
            }
          }
        },
        mouseDown: function (sender, me) {
          this.startX = me.getGraphX();
          this.startY = me.getGraphY();
          this.scrollLeft = graph.container.scrollLeft;
          this.scrollTop = graph.container.scrollTop;

          if (this.currentLink == null && graph.container.style.overflow == 'auto') {
            graph.container.style.cursor = 'move';
          }

          this.updateCurrentState(me);
        },
        mouseMove: function (sender, me) {
          if (graph.isMouseDown) {
            if (this.currentLink != null) {
              var dx = Math.abs(this.startX - me.getGraphX());
              var dy = Math.abs(this.startY - me.getGraphY());

              if (dx > tol || dy > tol) {
                this.clear();
              }
            }
          } else {
            // Checks for parent link
            var linkNode = me.getSource();

            while (linkNode != null && linkNode.nodeName.toLowerCase() != 'a') {
              linkNode = linkNode.parentNode;
            }

            if (linkNode != null) {
              this.clear();
            } else {
              if (graph.tooltipHandler != null && this.currentLink != null && this.currentState != null) {
                graph.tooltipHandler.reset(me, true, this.currentState);
              }

              if (
                this.currentState != null &&
                (me.getState() == this.currentState || me.sourceState == null) &&
                graph.intersects(this.currentState, me.getGraphX(), me.getGraphY())
              ) {
                return;
              }

              this.updateCurrentState(me);
            }
          }
        },
        mouseUp: function (sender, me) {
          var source = me.getSource();
          var evt = me.getEvent();

          // Checks for parent link
          var linkNode = source;

          while (linkNode != null && linkNode.nodeName.toLowerCase() != 'a') {
            linkNode = linkNode.parentNode;
          }

          // Ignores clicks on links and collapse/expand icon
          if (
            linkNode == null &&
            Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol &&
            Math.abs(this.scrollTop - graph.container.scrollTop) < tol &&
            (me.sourceState == null || !me.isSource(me.sourceState.control)) &&
            (((mxEvent.isLeftMouseButton(evt) || mxEvent.isMiddleMouseButton(evt)) && !mxEvent.isPopupTrigger(evt)) ||
              mxEvent.isTouchEvent(evt))
          ) {
            if (this.currentLink != null) {
              var blank = graph.isBlankLink(this.currentLink);

              if ((this.currentLink.substring(0, 5) === 'data:' || !blank) && beforeClick != null) {
                beforeClick(evt, this.currentLink);
              }

              if (!mxEvent.isConsumed(evt)) {
                var target = mxEvent.isMiddleMouseButton(evt) ? '_blank' : blank ? graph.linkTarget : '_top';
                graph.openLink(this.currentLink, target);
                me.consume();
              }
            } else if (
              onClick != null &&
              !me.isConsumed() &&
              Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol &&
              Math.abs(this.scrollTop - graph.container.scrollTop) < tol &&
              Math.abs(this.startX - me.getGraphX()) < tol &&
              Math.abs(this.startY - me.getGraphY()) < tol
            ) {
              onClick(me.getEvent());
            }
          }

          this.clear();
        },
        activate: function (state) {
          this.currentLink = graph.getAbsoluteUrl(graph.getLinkForCell(state.cell));

          if (this.currentLink != null) {
            graph.container.style.cursor = 'pointer';

            if (this.highlight != null) {
              this.highlight.highlight(state);
            }
          }
        },
        clear: function () {
          if (graph.container != null) {
            graph.container.style.cursor = cursor;
          }

          this.currentState = null;
          this.currentLink = null;

          if (this.highlight != null) {
            this.highlight.hide();
          }

          if (graph.tooltipHandler != null) {
            graph.tooltipHandler.hide();
          }
        },
      };

      // Ignores built-in click handling
      graph.click = function (me) {};
      graph.addMouseListener(mouseListener);

      mxEvent.addListener(document, 'mouseleave', function (evt) {
        mouseListener.clear();
      });
    };

    /**
     * Duplicates the given cells and returns the duplicates.
     */
    Graph.prototype.duplicateCells = function (cells, append) {
      cells = cells != null ? cells : this.getSelectionCells();
      append = append != null ? append : true;

      // Duplicates rows for table cells
      for (var i = 0; i < cells.length; i++) {
        if (this.isTableCell(cells[i])) {
          cells[i] = this.model.getParent(cells[i]);
        }
      }

      cells = this.model.getTopmostCells(cells);

      var model = this.getModel();
      var s = this.gridSize;
      var select = [];

      model.beginUpdate();
      try {
        var clones = this.cloneCells(cells, false, null, true);

        for (var i = 0; i < cells.length; i++) {
          var parent = model.getParent(cells[i]);
          var child = this.moveCells([clones[i]], s, s, false)[0];
          select.push(child);

          if (append) {
            model.add(parent, clones[i]);
          } else {
            // Maintains child index by inserting after clone in parent
            var index = parent.getIndex(cells[i]);
            model.add(parent, clones[i], index + 1);
          }

          // Extends tables
          if (this.isTable(parent)) {
            var row = this.getCellGeometry(clones[i]);
            var table = this.getCellGeometry(parent);

            if (row != null && table != null) {
              table = table.clone();
              table.height += row.height;
              model.setGeometry(parent, table);
            }
          }
        }
      } finally {
        model.endUpdate();
      }

      return select;
    };

    /**
     * Inserts the given image at the cursor in a content editable text box using
     * the insertimage command on the document instance.
     */
    Graph.prototype.insertImage = function (newValue, w, h) {
      // To find the new image, we create a list of all existing links first
      if (newValue != null && this.cellEditor.textarea != null) {
        var tmp = this.cellEditor.textarea.getElementsByTagName('img');
        var oldImages = [];

        for (var i = 0; i < tmp.length; i++) {
          oldImages.push(tmp[i]);
        }

        // LATER: Fix inserting link/image in IE8/quirks after focus lost
        document.execCommand('insertimage', false, newValue);

        // Sets size of new image
        var newImages = this.cellEditor.textarea.getElementsByTagName('img');

        if (newImages.length == oldImages.length + 1) {
          // Inverse order in favor of appended images
          for (var i = newImages.length - 1; i >= 0; i--) {
            if (i == 0 || newImages[i] != oldImages[i - 1]) {
              // Workaround for lost styles during undo and redo is using attributes
              newImages[i].setAttribute('width', w);
              newImages[i].setAttribute('height', h);

              break;
            }
          }
        }
      }
    };

    /**
     * Inserts the given image at the cursor in a content editable text box using
     * the insertimage command on the document instance.
     */
    Graph.prototype.insertLink = function (value) {
      if (this.cellEditor.textarea != null) {
        if (value.length == 0) {
          document.execCommand('unlink', false);
        } else if (mxClient.IS_FF) {
          // Workaround for Firefox that adds a new link and removes
          // the href from the inner link if its parent is a span is
          // to remove all inner links inside the new outer link
          var tmp = this.cellEditor.textarea.getElementsByTagName('a');
          var oldLinks = [];

          for (var i = 0; i < tmp.length; i++) {
            oldLinks.push(tmp[i]);
          }

          document.execCommand('createlink', false, mxUtils.trim(value));

          // Finds the new link element
          var newLinks = this.cellEditor.textarea.getElementsByTagName('a');

          if (newLinks.length == oldLinks.length + 1) {
            // Inverse order in favor of appended links
            for (var i = newLinks.length - 1; i >= 0; i--) {
              if (newLinks[i] != oldLinks[i - 1]) {
                // Removes all inner links from the new link and
                // moves the children to the inner link parent
                var tmp = newLinks[i].getElementsByTagName('a');

                while (tmp.length > 0) {
                  var parent = tmp[0].parentNode;

                  while (tmp[0].firstChild != null) {
                    parent.insertBefore(tmp[0].firstChild, tmp[0]);
                  }

                  parent.removeChild(tmp[0]);
                }

                break;
              }
            }
          }
        } else {
          // LATER: Fix inserting link/image in IE8/quirks after focus lost
          document.execCommand('createlink', false, mxUtils.trim(value));
        }
      }
    };

    /**
     *
     * @param cell
     * @returns {Boolean}
     */
    Graph.prototype.isCellResizable = function (cell) {
      var result = mxGraph.prototype.isCellResizable.apply(this, arguments);
      var style = this.getCurrentCellStyle(cell);

      return (
        !this.isTableCell(cell) &&
        !this.isTableRow(cell) &&
        (result ||
          (mxUtils.getValue(style, mxConstants.STYLE_RESIZABLE, '1') != '0' &&
            style[mxConstants.STYLE_WHITE_SPACE] == 'wrap'))
      );
    };

    /**
     * Function: distributeCells
     *
     * Distribuets the centers of the given cells equally along the available
     * horizontal or vertical space.
     *
     * Parameters:
     *
     * horizontal - Boolean that specifies the direction of the distribution.
     * cells - Optional array of <mxCells> to be distributed. Edges are ignored.
     */
    Graph.prototype.distributeCells = function (horizontal, cells) {
      if (cells == null) {
        cells = this.getSelectionCells();
      }

      if (cells != null && cells.length > 1) {
        var vertices = [];
        var max = null;
        var min = null;

        for (var i = 0; i < cells.length; i++) {
          if (this.getModel().isVertex(cells[i])) {
            var state = this.view.getState(cells[i]);

            if (state != null) {
              var tmp = horizontal ? state.getCenterX() : state.getCenterY();
              max = max != null ? Math.max(max, tmp) : tmp;
              min = min != null ? Math.min(min, tmp) : tmp;

              vertices.push(state);
            }
          }
        }

        if (vertices.length > 2) {
          vertices.sort(function (a, b) {
            return horizontal ? a.x - b.x : a.y - b.y;
          });

          var t = this.view.translate;
          var s = this.view.scale;

          min = min / s - (horizontal ? t.x : t.y);
          max = max / s - (horizontal ? t.x : t.y);

          this.getModel().beginUpdate();
          try {
            var dt = (max - min) / (vertices.length - 1);
            var t0 = min;

            for (var i = 1; i < vertices.length - 1; i++) {
              var pstate = this.view.getState(this.model.getParent(vertices[i].cell));
              var geo = this.getCellGeometry(vertices[i].cell);
              t0 += dt;

              if (geo != null && pstate != null) {
                geo = geo.clone();

                if (horizontal) {
                  geo.x = Math.round(t0 - geo.width / 2) - pstate.origin.x;
                } else {
                  geo.y = Math.round(t0 - geo.height / 2) - pstate.origin.y;
                }

                this.getModel().setGeometry(vertices[i].cell, geo);
              }
            }
          } finally {
            this.getModel().endUpdate();
          }
        }
      }

      return cells;
    };

    /**
     * Adds meta-drag an Mac.
     * @param evt
     * @returns
     */
    Graph.prototype.isCloneEvent = function (evt) {
      return (mxClient.IS_MAC && mxEvent.isMetaDown(evt)) || mxEvent.isControlDown(evt);
    };

    /**
     * Translates this point by the given vector.
     *
     * @param {number} dx X-coordinate of the translation.
     * @param {number} dy Y-coordinate of the translation.
     */
    Graph.prototype.createSvgImageExport = function () {
      var exp = new mxImageExport();

      // Adds hyperlinks (experimental)
      exp.getLinkForCellState = mxUtils.bind(this, function (state, canvas) {
        return this.getLinkForCell(state.cell);
      });

      return exp;
    };

    /**
     * Translates this point by the given vector.
     *
     * @param {number} dx X-coordinate of the translation.
     * @param {number} dy Y-coordinate of the translation.
     */
    Graph.prototype.getSvg = function (
      background,
      scale,
      border,
      nocrop,
      crisp,
      ignoreSelection,
      showText,
      imgExport,
      linkTarget,
      hasShadow
    ) {
      //Disable Css Transforms if it is used
      var origUseCssTrans = this.useCssTransforms;

      if (origUseCssTrans) {
        this.useCssTransforms = false;
        this.view.revalidate();
        this.sizeDidChange();
      }

      try {
        scale = scale != null ? scale : 1;
        border = border != null ? border : 0;
        crisp = crisp != null ? crisp : true;
        ignoreSelection = ignoreSelection != null ? ignoreSelection : true;
        showText = showText != null ? showText : true;

        var bounds = ignoreSelection || nocrop ? this.getGraphBounds() : this.getBoundingBox(this.getSelectionCells());

        if (bounds == null) {
          throw Error(mxResources.get('drawingEmpty'));
        }

        var vs = this.view.scale;

        // Prepares SVG document that holds the output
        var svgDoc = mxUtils.createXmlDocument();
        var root =
          svgDoc.createElementNS != null
            ? svgDoc.createElementNS(mxConstants.NS_SVG, 'svg')
            : svgDoc.createElement('svg');

        if (background != null) {
          if (root.style != null) {
            root.style.backgroundColor = background;
          } else {
            root.setAttribute('style', 'background-color:' + background);
          }
        }

        if (svgDoc.createElementNS == null) {
          root.setAttribute('xmlns', mxConstants.NS_SVG);
          root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);
        } else {
          // KNOWN: Ignored in IE9-11, adds namespace for each image element instead. No workaround.
          root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);
        }

        var s = scale / vs;
        var w = Math.max(1, Math.ceil(bounds.width * s) + 2 * border) + (hasShadow ? 5 : 0);
        var h = Math.max(1, Math.ceil(bounds.height * s) + 2 * border) + (hasShadow ? 5 : 0);

        root.setAttribute('version', '1.1');
        root.setAttribute('width', w + 'px');
        root.setAttribute('height', h + 'px');
        root.setAttribute('viewBox', (crisp ? '-0.5 -0.5' : '0 0') + ' ' + w + ' ' + h);
        svgDoc.appendChild(root);

        // Renders graph. Offset will be multiplied with state's scale when painting state.
        // TextOffset only seems to affect FF output but used everywhere for consistency.
        var group =
          svgDoc.createElementNS != null ? svgDoc.createElementNS(mxConstants.NS_SVG, 'g') : svgDoc.createElement('g');
        root.appendChild(group);

        var svgCanvas = this.createSvgCanvas(group);
        svgCanvas.foOffset = crisp ? -0.5 : 0;
        svgCanvas.textOffset = crisp ? -0.5 : 0;
        svgCanvas.imageOffset = crisp ? -0.5 : 0;
        svgCanvas.translate(Math.floor((border / scale - bounds.x) / vs), Math.floor((border / scale - bounds.y) / vs));

        // Convert HTML entities
        var htmlConverter = document.createElement('div');

        // Adds simple text fallback for viewers with no support for foreignObjects
        var getAlternateText = svgCanvas.getAlternateText;
        svgCanvas.getAlternateText = function (
          fo,
          x,
          y,
          w,
          h,
          str,
          align,
          valign,
          wrap,
          format,
          overflow,
          clip,
          rotation
        ) {
          // Assumes a max character width of 0.5em
          if (str != null && this.state.fontSize > 0) {
            try {
              if (mxUtils.isNode(str)) {
                str = str.innerText;
              } else {
                htmlConverter.innerHTML = str;
                str = mxUtils.extractTextWithWhitespace(htmlConverter.childNodes);
              }

              // Workaround for substring breaking double byte UTF
              var exp = Math.ceil((2 * w) / this.state.fontSize);
              var result = [];
              var length = 0;
              var index = 0;

              while ((exp == 0 || length < exp) && index < str.length) {
                var char = str.charCodeAt(index);

                if (char == 10 || char == 13) {
                  if (length > 0) {
                    break;
                  }
                } else {
                  result.push(str.charAt(index));

                  if (char < 255) {
                    length++;
                  }
                }

                index++;
              }

              // Uses result and adds ellipsis if more than 1 char remains
              if (result.length < str.length && str.length - result.length > 1) {
                str = mxUtils.trim(result.join('')) + '...';
              }

              return str;
            } catch (e) {
              return getAlternateText.apply(this, arguments);
            }
          } else {
            return getAlternateText.apply(this, arguments);
          }
        };

        // Paints background image
        var bgImg = this.backgroundImage;

        if (bgImg != null) {
          var s2 = vs / scale;
          var tr = this.view.translate;
          var tmp = new mxRectangle(tr.x * s2, tr.y * s2, bgImg.width * s2, bgImg.height * s2);

          // Checks if visible
          if (mxUtils.intersects(bounds, tmp)) {
            svgCanvas.image(tr.x, tr.y, bgImg.width, bgImg.height, bgImg.src, true);
          }
        }

        svgCanvas.scale(s);
        svgCanvas.textEnabled = showText;

        imgExport = imgExport != null ? imgExport : this.createSvgImageExport();
        var imgExportDrawCellState = imgExport.drawCellState;

        // Ignores custom links
        var imgExportGetLinkForCellState = imgExport.getLinkForCellState;

        imgExport.getLinkForCellState = function (state, canvas) {
          var result = imgExportGetLinkForCellState.apply(this, arguments);

          return result != null && !state.view.graph.isCustomLink(result) ? result : null;
        };

        // Implements ignoreSelection flag
        imgExport.drawCellState = function (state, canvas) {
          var graph = state.view.graph;
          var selected = graph.isCellSelected(state.cell);
          var parent = graph.model.getParent(state.cell);

          // Checks if parent cell is selected
          while (!ignoreSelection && !selected && parent != null) {
            selected = graph.isCellSelected(parent);
            parent = graph.model.getParent(parent);
          }

          if (ignoreSelection || selected) {
            imgExportDrawCellState.apply(this, arguments);
          }
        };

        imgExport.drawState(this.getView().getState(this.model.root), svgCanvas);
        this.updateSvgLinks(root, linkTarget, true);
        this.addForeignObjectWarning(svgCanvas, root);

        return root;
      } finally {
        if (origUseCssTrans) {
          this.useCssTransforms = true;
          this.view.revalidate();
          this.sizeDidChange();
        }
      }
    };

    /**
     * Adds warning for truncated labels in older viewers.
     */
    Graph.prototype.addForeignObjectWarning = function (canvas, root) {
      if (root.getElementsByTagName('foreignObject').length > 0) {
        var sw = canvas.createElement('switch');
        var g1 = canvas.createElement('g');
        g1.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility');
        var a = canvas.createElement('a');
        a.setAttribute('transform', 'translate(0,-5)');

        // Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below
        // in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.
        if (a.setAttributeNS == null || (root.ownerDocument != document && document.documentMode == null)) {
          a.setAttribute('xlink:href', Graph.foreignObjectWarningLink);
          a.setAttribute('target', '_blank');
        } else {
          a.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', Graph.foreignObjectWarningLink);
          a.setAttributeNS(mxConstants.NS_XLINK, 'target', '_blank');
        }

        var text = canvas.createElement('text');
        text.setAttribute('text-anchor', 'middle');
        text.setAttribute('font-size', '10px');
        text.setAttribute('x', '50%');
        text.setAttribute('y', '100%');
        mxUtils.write(text, Graph.foreignObjectWarningText);

        sw.appendChild(g1);
        a.appendChild(text);
        sw.appendChild(a);
        root.appendChild(sw);
      }
    };

    /**
     * Hook for creating the canvas used in getSvg.
     */
    Graph.prototype.updateSvgLinks = function (node, target, removeCustom) {
      var links = node.getElementsByTagName('a');

      for (var i = 0; i < links.length; i++) {
        var href = links[i].getAttribute('href');

        if (href == null) {
          href = links[i].getAttribute('xlink:href');
        }

        if (href != null) {
          if (target != null && /^https?:\/\//.test(href)) {
            links[i].setAttribute('target', target);
          } else if (removeCustom && this.isCustomLink(href)) {
            links[i].setAttribute('href', 'javascript:void(0);');
          }
        }
      }
    };

    /**
     * Hook for creating the canvas used in getSvg.
     */
    Graph.prototype.createSvgCanvas = function (node) {
      var canvas = new mxSvgCanvas2D(node);

      canvas.pointerEvents = true;

      return canvas;
    };

    /**
     * Returns the first ancestor of the current selection with the given name.
     */
    Graph.prototype.getSelectedElement = function () {
      var node = null;

      if (window.getSelection) {
        var sel = window.getSelection();

        if (sel.getRangeAt && sel.rangeCount) {
          var range = sel.getRangeAt(0);
          node = range.commonAncestorContainer;
        }
      } else if (document.selection) {
        node = document.selection.createRange().parentElement();
      }

      return node;
    };

    /**
     * Returns the text editing element.
     */
    Graph.prototype.getSelectedEditingElement = function () {
      var node = this.getSelectedElement();

      while (node != null && node.nodeType != mxConstants.NODETYPE_ELEMENT) {
        node = node.parentNode;
      }

      if (node != null) {
        // Workaround for commonAncestor on range in IE11 returning parent of common ancestor
        if (
          node == this.cellEditor.textarea &&
          this.cellEditor.textarea.children.length == 1 &&
          this.cellEditor.textarea.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT
        ) {
          node = this.cellEditor.textarea.firstChild;
        }
      }

      return node;
    };

    /**
     * Returns the first ancestor of the current selection with the given name.
     */
    Graph.prototype.getParentByName = function (node, name, stopAt) {
      while (node != null) {
        if (node.nodeName == name) {
          return node;
        }

        if (node == stopAt) {
          return null;
        }

        node = node.parentNode;
      }

      return node;
    };

    /**
     * Returns the first ancestor of the current selection with the given name.
     */
    Graph.prototype.getParentByNames = function (node, names, stopAt) {
      while (node != null) {
        if (mxUtils.indexOf(names, node.nodeName) >= 0) {
          return node;
        }

        if (node == stopAt) {
          return null;
        }

        node = node.parentNode;
      }

      return node;
    };

    /**
     * Selects the given node.
     */
    Graph.prototype.selectNode = function (node) {
      var sel = null;

      // IE9 and non-IE
      if (window.getSelection) {
        sel = window.getSelection();

        if (sel.getRangeAt && sel.rangeCount) {
          var range = document.createRange();
          range.selectNode(node);
          sel.removeAllRanges();
          sel.addRange(range);
        }
      }
      // IE < 9
      else if ((sel = document.selection) && sel.type != 'Control') {
        var originalRange = sel.createRange();
        originalRange.collapse(true);
        var range = sel.createRange();
        range.setEndPoint('StartToStart', originalRange);
        range.select();
      }
    };

    /**
     * Deletes the given cells  and returns the cells to be selected.
     */
    Graph.prototype.deleteCells = function (cells, includeEdges) {
      var select = null;

      if (cells != null && cells.length > 0) {
        this.model.beginUpdate();
        try {
          // Shrinks tables
          for (var i = 0; i < cells.length; i++) {
            var parent = this.model.getParent(cells[i]);

            if (this.isTable(parent)) {
              var row = this.getCellGeometry(cells[i]);
              var table = this.getCellGeometry(parent);

              if (row != null && table != null) {
                table = table.clone();
                table.height -= row.height;
                this.model.setGeometry(parent, table);
              }
            }
          }

          var parents = this.selectParentAfterDelete ? this.model.getParents(cells) : null;
          this.removeCells(cells, includeEdges);
        } finally {
          this.model.endUpdate();
        }

        // Selects parents for easier editing of groups
        if (parents != null) {
          select = [];

          for (var i = 0; i < parents.length; i++) {
            if (this.model.contains(parents[i]) && (this.model.isVertex(parents[i]) || this.model.isEdge(parents[i]))) {
              select.push(parents[i]);
            }
          }
        }
      }

      return select;
    };

    /**
     * Inserts a column in the table for the given cell.
     */
    Graph.prototype.insertTableColumn = function (cell, before) {
      var model = this.getModel();
      model.beginUpdate();

      try {
        var table = cell;
        var index = 0;

        if (this.isTableCell(cell)) {
          var row = model.getParent(cell);
          table = model.getParent(row);
          index = mxUtils.indexOf(model.getChildCells(row, true), cell);
        } else {
          if (this.isTableRow(cell)) {
            table = model.getParent(cell);
          } else {
            cell = model.getChildCells(table, true)[0];
          }

          if (!before) {
            index = model.getChildCells(cell, true).length - 1;
          }
        }

        var rows = model.getChildCells(table, true);
        var dw = Graph.minTableColumnWidth;

        for (var i = 0; i < rows.length; i++) {
          var child = model.getChildCells(rows[i], true)[index];
          var clone = model.cloneCell(child, false);
          var geo = this.getCellGeometry(clone);
          clone.value = null;

          if (geo != null) {
            dw = geo.width;
            var rowGeo = this.getCellGeometry(rows[i]);

            if (rowGeo != null) {
              geo.height = rowGeo.height;
            }
          }

          model.add(rows[i], clone, index + (before ? 0 : 1));
        }

        var tableGeo = this.getCellGeometry(table);

        if (tableGeo != null) {
          tableGeo = tableGeo.clone();
          tableGeo.width += dw;

          model.setGeometry(table, tableGeo);
        }
      } finally {
        model.endUpdate();
      }
    };

    /**
     * Inserts a row in the table for the given cell.
     */
    Graph.prototype.insertTableRow = function (cell, before) {
      var model = this.getModel();
      model.beginUpdate();

      try {
        var table = cell;
        var row = cell;

        if (this.isTableCell(cell)) {
          row = model.getParent(cell);
          table = model.getParent(row);
        } else if (this.isTableRow(cell)) {
          table = model.getParent(cell);
        } else {
          var rows = model.getChildCells(table, true);
          row = rows[before ? 0 : rows.length - 1];
        }

        var cells = model.getChildCells(row, true);
        var index = table.getIndex(row);
        row = model.cloneCell(row, false);
        row.value = null;

        var rowGeo = this.getCellGeometry(row);

        if (rowGeo != null) {
          for (var i = 0; i < cells.length; i++) {
            var cell = model.cloneCell(cells[i], false);
            row.insert(cell);
            cell.value = null;

            var geo = this.getCellGeometry(cell);

            if (geo != null) {
              geo.height = rowGeo.height;
            }
          }

          model.add(table, row, index + (before ? 0 : 1));

          var tableGeo = this.getCellGeometry(table);

          if (tableGeo != null) {
            tableGeo = tableGeo.clone();
            tableGeo.height += rowGeo.height;

            model.setGeometry(table, tableGeo);
          }
        }
      } finally {
        model.endUpdate();
      }
    };

    /**
     *
     */
    Graph.prototype.deleteTableColumn = function (cell) {
      var model = this.getModel();
      model.beginUpdate();

      try {
        var table = cell;
        var row = cell;

        if (this.isTableCell(cell)) {
          row = model.getParent(cell);
        }

        if (this.isTableRow(row)) {
          table = model.getParent(row);
        }

        var rows = model.getChildCells(table, true);

        if (rows.length == 0) {
          model.remove(table);
        } else {
          if (!this.isTableRow(row)) {
            row = rows[0];
          }

          var cells = model.getChildCells(row, true);

          if (cells.length <= 1) {
            model.remove(table);
          } else {
            var index = cells.length - 1;

            if (this.isTableCell(cell)) {
              index = mxUtils.indexOf(cells, cell);
            }

            var width = 0;

            for (var i = 0; i < rows.length; i++) {
              var child = model.getChildCells(rows[i], true)[index];
              model.remove(child);

              var geo = this.getCellGeometry(child);

              if (geo != null) {
                width = Math.max(width, geo.width);
              }
            }

            var tableGeo = this.getCellGeometry(table);

            if (tableGeo != null) {
              tableGeo = tableGeo.clone();
              tableGeo.width -= width;

              model.setGeometry(table, tableGeo);
            }
          }
        }
      } finally {
        model.endUpdate();
      }
    };

    /**
     *
     */
    Graph.prototype.deleteTableRow = function (cell) {
      var model = this.getModel();
      model.beginUpdate();

      try {
        var table = cell;
        var row = cell;

        if (this.isTableCell(cell)) {
          row = model.getParent(cell);
          cell = row;
        }

        if (this.isTableRow(cell)) {
          table = model.getParent(row);
        }

        var rows = model.getChildCells(table, true);

        if (rows.length <= 1) {
          model.remove(table);
        } else {
          if (!this.isTableRow(row)) {
            row = rows[rows.length - 1];
          }

          model.remove(row);
          var height = 0;

          var geo = this.getCellGeometry(row);

          if (geo != null) {
            height = geo.height;
          }

          var tableGeo = this.getCellGeometry(table);

          if (tableGeo != null) {
            tableGeo = tableGeo.clone();
            tableGeo.height -= height;

            model.setGeometry(table, tableGeo);
          }
        }
      } finally {
        model.endUpdate();
      }
    };

    /**
     * Inserts a new row into the given table.
     */
    Graph.prototype.insertRow = function (table, index) {
      var bd = table.tBodies[0];
      var cells = bd.rows[0].cells;
      var cols = 0;

      // Counts columns including colspans
      for (var i = 0; i < cells.length; i++) {
        var colspan = cells[i].getAttribute('colspan');
        cols += colspan != null ? parseInt(colspan) : 1;
      }

      var row = bd.insertRow(index);

      for (var i = 0; i < cols; i++) {
        mxUtils.br(row.insertCell(-1));
      }

      return row.cells[0];
    };

    /**
     * Deletes the given column.
     */
    Graph.prototype.deleteRow = function (table, index) {
      table.tBodies[0].deleteRow(index);
    };

    /**
     * Deletes the given column.
     */
    Graph.prototype.insertColumn = function (table, index) {
      var hd = table.tHead;

      if (hd != null) {
        // TODO: use colIndex
        for (var h = 0; h < hd.rows.length; h++) {
          var th = document.createElement('th');
          hd.rows[h].appendChild(th);
          mxUtils.br(th);
        }
      }

      var bd = table.tBodies[0];

      for (var i = 0; i < bd.rows.length; i++) {
        var cell = bd.rows[i].insertCell(index);
        mxUtils.br(cell);
      }

      return bd.rows[0].cells[index >= 0 ? index : bd.rows[0].cells.length - 1];
    };

    /**
     * Deletes the given column.
     */
    Graph.prototype.deleteColumn = function (table, index) {
      if (index >= 0) {
        var bd = table.tBodies[0];
        var rows = bd.rows;

        for (var i = 0; i < rows.length; i++) {
          if (rows[i].cells.length > index) {
            rows[i].deleteCell(index);
          }
        }
      }
    };

    /**
     * Inserts the given HTML at the caret position (no undo).
     */
    Graph.prototype.pasteHtmlAtCaret = function (html) {
      var sel, range;

      // IE9 and non-IE
      if (window.getSelection) {
        sel = window.getSelection();

        if (sel.getRangeAt && sel.rangeCount) {
          range = sel.getRangeAt(0);
          range.deleteContents();

          // Range.createContextualFragment() would be useful here but is
          // only relatively recently standardized and is not supported in
          // some browsers (IE9, for one)
          var el = document.createElement('div');
          el.innerHTML = html;
          var frag = document.createDocumentFragment(),
            node;

          while ((node = el.firstChild)) {
            lastNode = frag.appendChild(node);
          }

          range.insertNode(frag);
        }
      }
      // IE < 9
      else if ((sel = document.selection) && sel.type != 'Control') {
        // FIXME: Does not work if selection is empty
        sel.createRange().pasteHTML(html);
      }
    };

    /**
     * Creates an anchor elements for handling the given link in the
     * hint that is shown when the cell is selected.
     */
    Graph.prototype.createLinkForHint = function (link, label) {
      link = link != null ? link : 'javascript:void(0);';

      if (label == null || label.length == 0) {
        if (this.isCustomLink(link)) {
          label = this.getLinkTitle(link);
        } else {
          label = link;
        }
      }

      // Helper function to shorten strings
      function short(str, max) {
        if (str.length > max) {
          str = str.substring(0, Math.round(max / 2)) + '...' + str.substring(str.length - Math.round(max / 4));
        }

        return str;
      }

      var a = document.createElement('a');
      a.setAttribute('rel', this.linkRelation);
      a.setAttribute('href', this.getAbsoluteUrl(link));
      a.setAttribute('title', short(this.isCustomLink(link) ? this.getLinkTitle(link) : link, 80));

      if (this.linkTarget != null) {
        a.setAttribute('target', this.linkTarget);
      }

      // Adds shortened label to link
      mxUtils.write(a, short(label, 40));

      // Handles custom links
      if (this.isCustomLink(link)) {
        mxEvent.addListener(
          a,
          'click',
          mxUtils.bind(this, function (evt) {
            this.customLinkClicked(link);
            mxEvent.consume(evt);
          })
        );
      }

      return a;
    };

    /**
     * Customized graph for touch devices.
     */
    Graph.prototype.initTouch = function () {
      // Disables new connections via "hotspot"
      this.connectionHandler.marker.isEnabled = function () {
        return this.graph.connectionHandler.first != null;
      };

      // Hides menu when editing starts
      this.addListener(mxEvent.START_EDITING, function (sender, evt) {
        this.popupMenuHandler.hideMenu();
      });

      // Adds custom hit detection if native hit detection found no cell
      var graphUpdateMouseEvent = this.updateMouseEvent;
      this.updateMouseEvent = function (me) {
        me = graphUpdateMouseEvent.apply(this, arguments);

        if (mxEvent.isTouchEvent(me.getEvent()) && me.getState() == null) {
          var cell = this.getCellAt(me.graphX, me.graphY);

          if (cell != null && this.isSwimlane(cell) && this.hitsSwimlaneContent(cell, me.graphX, me.graphY)) {
            cell = null;
          } else {
            me.state = this.view.getState(cell);

            if (me.state != null && me.state.shape != null) {
              this.container.style.cursor = me.state.shape.node.style.cursor;
            }
          }
        }

        if (me.getState() == null && this.isEnabled()) {
          this.container.style.cursor = 'default';
        }

        return me;
      };

      // Context menu trigger implementation depending on current selection state
      // combined with support for normal popup trigger.
      var cellSelected = false;
      var selectionEmpty = false;
      var menuShowing = false;

      var oldFireMouseEvent = this.fireMouseEvent;

      this.fireMouseEvent = function (evtName, me, sender) {
        if (evtName == mxEvent.MOUSE_DOWN) {
          // For hit detection on edges
          me = this.updateMouseEvent(me);

          cellSelected = this.isCellSelected(me.getCell());
          selectionEmpty = this.isSelectionEmpty();
          menuShowing = this.popupMenuHandler.isMenuShowing();
        }

        oldFireMouseEvent.apply(this, arguments);
      };

      // Shows popup menu if cell was selected or selection was empty and background was clicked
      // FIXME: Conflicts with mxPopupMenuHandler.prototype.getCellForPopupEvent in Editor.js by
      // selecting parent for selected children in groups before this check can be made.
      this.popupMenuHandler.mouseUp = mxUtils.bind(this, function (sender, me) {
        this.popupMenuHandler.popupTrigger =
          !this.isEditing() &&
          this.isEnabled() &&
          (me.getState() == null || !me.isSource(me.getState().control)) &&
          (this.popupMenuHandler.popupTrigger ||
            (!menuShowing &&
              !mxEvent.isMouseEvent(me.getEvent()) &&
              ((selectionEmpty && me.getCell() == null && this.isSelectionEmpty()) ||
                (cellSelected && this.isCellSelected(me.getCell())))));
        mxPopupMenuHandler.prototype.mouseUp.apply(this.popupMenuHandler, arguments);
      });
    };

    /**
     * HTML in-place editor
     */
    mxCellEditor.prototype.isContentEditing = function () {
      var state = this.graph.view.getState(this.editingCell);

      return state != null && state.style['html'] == 1;
    };

    /**
     * Returns true if all selected text is inside a table element.
     */
    mxCellEditor.prototype.isTableSelected = function () {
      return this.graph.getParentByName(this.graph.getSelectedElement(), 'TABLE', this.textarea) != null;
    };

    /**
     * Sets the alignment of the current selected cell. This sets the
     * alignment in the cell style, removes all alignment within the
     * text and invokes the built-in alignment function.
     *
     * Only the built-in function is invoked if shift is pressed or
     * if table cells are selected and shift is not pressed.
     */
    mxCellEditor.prototype.alignText = function (align, evt) {
      var shiftPressed = evt != null && mxEvent.isShiftDown(evt);

      if (shiftPressed || (window.getSelection != null && window.getSelection().containsNode != null)) {
        var allSelected = true;

        this.graph.processElements(this.textarea, function (node) {
          if (shiftPressed || window.getSelection().containsNode(node, true)) {
            node.removeAttribute('align');
            node.style.textAlign = null;
          } else {
            allSelected = false;
          }
        });

        if (allSelected) {
          this.graph.cellEditor.setAlign(align);
        }
      }

      document.execCommand('justify' + align.toLowerCase(), false, null);
    };

    /**
     * Creates the keyboard event handler for the current graph and history.
     */
    mxCellEditor.prototype.saveSelection = function () {
      if (window.getSelection) {
        var sel = window.getSelection();

        if (sel.getRangeAt && sel.rangeCount) {
          var ranges = [];

          for (var i = 0, len = sel.rangeCount; i < len; ++i) {
            ranges.push(sel.getRangeAt(i));
          }

          return ranges;
        }
      } else if (document.selection && document.selection.createRange) {
        return document.selection.createRange();
      }

      return null;
    };

    /**
     * Creates the keyboard event handler for the current graph and history.
     */
    mxCellEditor.prototype.restoreSelection = function (savedSel) {
      try {
        if (savedSel) {
          if (window.getSelection) {
            sel = window.getSelection();
            sel.removeAllRanges();

            for (var i = 0, len = savedSel.length; i < len; ++i) {
              sel.addRange(savedSel[i]);
            }
          } else if (document.selection && savedSel.select) {
            savedSel.select();
          }
        }
      } catch (e) {
        // ignore
      }
    };

    /**
     * Handling of special nl2Br style for not converting newlines to breaks in HTML labels.
     * NOTE: Since it's easier to set this when the label is created we assume that it does
     * not change during the lifetime of the mxText instance.
     */
    var mxCellRendererInitializeLabel = mxCellRenderer.prototype.initializeLabel;
    mxCellRenderer.prototype.initializeLabel = function (state) {
      if (state.text != null) {
        state.text.replaceLinefeeds = mxUtils.getValue(state.style, 'nl2Br', '1') != '0';
      }

      mxCellRendererInitializeLabel.apply(this, arguments);
    };

    var mxConstraintHandlerUpdate = mxConstraintHandler.prototype.update;
    mxConstraintHandler.prototype.update = function (me, source) {
      if (this.isKeepFocusEvent(me) || !mxEvent.isAltDown(me.getEvent())) {
        mxConstraintHandlerUpdate.apply(this, arguments);
      } else {
        this.reset();
      }
    };

    /**
     * No dashed shapes.
     */
    mxGuide.prototype.createGuideShape = function (horizontal) {
      var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);

      return guide;
    };

    /**
     * HTML in-place editor
     */
    mxCellEditor.prototype.escapeCancelsEditing = false;

    /**
     * Overridden to set CSS classes.
     */
    var mxCellEditorStartEditing = mxCellEditor.prototype.startEditing;
    mxCellEditor.prototype.startEditing = function (cell, trigger) {
      cell = this.graph.getStartEditingCell(cell, trigger);

      mxCellEditorStartEditing.apply(this, arguments);

      // Overrides class in case of HTML content to add
      // dashed borders for divs and table cells
      var state = this.graph.view.getState(cell);

      if (state != null && state.style['html'] == 1) {
        this.textarea.className = 'mxCellEditor geContentEditable';
      } else {
        this.textarea.className = 'mxCellEditor mxPlainTextEditor';
      }

      // Toggles markup vs wysiwyg mode
      this.codeViewMode = false;

      // Stores current selection range when switching between markup and code
      this.switchSelectionState = null;

      // Selects editing cell
      this.graph.setSelectionCell(cell);

      // Enables focus outline for edges and edge labels
      var parent = this.graph.getModel().getParent(cell);
      var geo = this.graph.getCellGeometry(cell);

      if ((this.graph.getModel().isEdge(parent) && geo != null && geo.relative) || this.graph.getModel().isEdge(cell)) {
        // Quirks does not support outline at all so use border instead
        if (mxClient.IS_QUIRKS) {
          this.textarea.style.border = 'gray dotted 1px';
        }
        // IE>8 and FF on Windows uses outline default of none
        else if (mxClient.IS_IE || mxClient.IS_IE11 || (mxClient.IS_FF && mxClient.IS_WIN)) {
          this.textarea.style.outline = 'gray dotted 1px';
        } else {
          this.textarea.style.outline = '';
        }
      } else if (mxClient.IS_QUIRKS) {
        this.textarea.style.outline = 'none';
        this.textarea.style.border = '';
      }
    };

    /**
     * HTML in-place editor
     */
    var cellEditorInstallListeners = mxCellEditor.prototype.installListeners;
    mxCellEditor.prototype.installListeners = function (elt) {
      cellEditorInstallListeners.apply(this, arguments);

      // Adds a reference from the clone to the original node, recursively
      function reference(node, clone) {
        clone.originalNode = node;

        node = node.firstChild;
        var child = clone.firstChild;

        while (node != null && child != null) {
          reference(node, child);
          node = node.nextSibling;
          child = child.nextSibling;
        }

        return clone;
      }

      // Checks the given node for new nodes, recursively
      function checkNode(node, clone) {
        if (node != null) {
          if (clone.originalNode != node) {
            cleanNode(node);
          } else {
            node = node.firstChild;
            clone = clone.firstChild;

            while (node != null) {
              var nextNode = node.nextSibling;

              if (clone == null) {
                cleanNode(node);
              } else {
                checkNode(node, clone);
                clone = clone.nextSibling;
              }

              node = nextNode;
            }
          }
        }
      }

      // Removes unused DOM nodes and attributes, recursively
      function cleanNode(node) {
        var child = node.firstChild;

        while (child != null) {
          var next = child.nextSibling;
          cleanNode(child);
          child = next;
        }

        if (
          (node.nodeType != 1 || (node.nodeName !== 'BR' && node.firstChild == null)) &&
          (node.nodeType != 3 || mxUtils.trim(mxUtils.getTextContent(node)).length == 0)
        ) {
          node.parentNode.removeChild(node);
        } else {
          // Removes linefeeds
          if (node.nodeType == 3) {
            mxUtils.setTextContent(node, mxUtils.getTextContent(node).replace(/\n|\r/g, ''));
          }

          // Removes CSS classes and styles (for Word and Excel)
          if (node.nodeType == 1) {
            node.removeAttribute('style');
            node.removeAttribute('class');
            node.removeAttribute('width');
            node.removeAttribute('cellpadding');
            node.removeAttribute('cellspacing');
            node.removeAttribute('border');
          }
        }
      }

      // Handles paste from Word, Excel etc by removing styles, classnames and unused nodes
      // LATER: Fix undo/redo for paste
      if (!mxClient.IS_QUIRKS && document.documentMode !== 7 && document.documentMode !== 8) {
        mxEvent.addListener(
          this.textarea,
          'paste',
          mxUtils.bind(this, function (evt) {
            var clone = reference(this.textarea, this.textarea.cloneNode(true));

            window.setTimeout(
              mxUtils.bind(this, function () {
                if (this.textarea != null) {
                  // Paste from Word or Excel
                  if (
                    this.textarea.innerHTML.indexOf('<o:OfficeDocumentSettings>') >= 0 ||
                    this.textarea.innerHTML.indexOf('<!--[if !mso]>') >= 0
                  ) {
                    checkNode(this.textarea, clone);
                  } else {
                    Graph.removePasteFormatting(this.textarea);
                  }
                }
              }),
              0
            );
          })
        );
      }
    };

    mxCellEditor.prototype.toggleViewMode = function () {
      var state = this.graph.view.getState(this.editingCell);

      if (state != null) {
        var nl2Br = state != null && mxUtils.getValue(state.style, 'nl2Br', '1') != '0';
        var tmp = this.saveSelection();

        if (!this.codeViewMode) {
          // Clears the initial empty label on the first keystroke
          if (this.clearOnChange && this.textarea.innerHTML == this.getEmptyLabelText()) {
            this.clearOnChange = false;
            this.textarea.innerHTML = '';
          }

          // Removes newlines from HTML and converts breaks to newlines
          // to match the HTML output in plain text
          var content = mxUtils.htmlEntities(this.textarea.innerHTML);

          // Workaround for trailing line breaks being ignored in the editor
          if (!mxClient.IS_QUIRKS && document.documentMode != 8) {
            content = mxUtils.replaceTrailingNewlines(content, '<div><br></div>');
          }

          content = this.graph.sanitizeHtml(
            nl2Br ? content.replace(/\n/g, '').replace(/&lt;br\s*.?&gt;/g, '<br>') : content,
            true
          );
          this.textarea.className = 'mxCellEditor mxPlainTextEditor';

          var size = mxConstants.DEFAULT_FONTSIZE;

          this.textarea.style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT
            ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px'
            : mxConstants.LINE_HEIGHT;
          this.textarea.style.fontSize = Math.round(size) + 'px';
          this.textarea.style.textDecoration = '';
          this.textarea.style.fontWeight = 'normal';
          this.textarea.style.fontStyle = '';
          this.textarea.style.fontFamily = mxConstants.DEFAULT_FONTFAMILY;
          this.textarea.style.textAlign = 'left';

          // Adds padding to make cursor visible with borders
          this.textarea.style.padding = '2px';

          if (this.textarea.innerHTML != content) {
            this.textarea.innerHTML = content;
          }

          this.codeViewMode = true;
        } else {
          var content = mxUtils.extractTextWithWhitespace(this.textarea.childNodes);

          // Strips trailing line break
          if (content.length > 0 && content.charAt(content.length - 1) == '\n') {
            content = content.substring(0, content.length - 1);
          }

          content = this.graph.sanitizeHtml(nl2Br ? content.replace(/\n/g, '<br/>') : content, true);
          this.textarea.className = 'mxCellEditor geContentEditable';

          var size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE);
          var family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY);
          var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
          var bold =
            (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_BOLD) ==
            mxConstants.FONT_BOLD;
          var italic =
            (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_ITALIC) ==
            mxConstants.FONT_ITALIC;
          var txtDecor = [];

          if (
            (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_UNDERLINE) ==
            mxConstants.FONT_UNDERLINE
          ) {
            txtDecor.push('underline');
          }

          if (
            (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) & mxConstants.FONT_STRIKETHROUGH) ==
            mxConstants.FONT_STRIKETHROUGH
          ) {
            txtDecor.push('line-through');
          }

          this.textarea.style.lineHeight = mxConstants.ABSOLUTE_LINE_HEIGHT
            ? Math.round(size * mxConstants.LINE_HEIGHT) + 'px'
            : mxConstants.LINE_HEIGHT;
          this.textarea.style.fontSize = Math.round(size) + 'px';
          this.textarea.style.textDecoration = txtDecor.join(' ');
          this.textarea.style.fontWeight = bold ? 'bold' : 'normal';
          this.textarea.style.fontStyle = italic ? 'italic' : '';
          this.textarea.style.fontFamily = family;
          this.textarea.style.textAlign = align;
          this.textarea.style.padding = '0px';

          if (this.textarea.innerHTML != content) {
            this.textarea.innerHTML = content;

            if (this.textarea.innerHTML.length == 0) {
              this.textarea.innerHTML = this.getEmptyLabelText();
              this.clearOnChange = this.textarea.innerHTML.length > 0;
            }
          }

          this.codeViewMode = false;
        }

        this.textarea.focus();

        if (this.switchSelectionState != null) {
          this.restoreSelection(this.switchSelectionState);
        }

        this.switchSelectionState = tmp;
        this.resize();
      }
    };

    var mxCellEditorResize = mxCellEditor.prototype.resize;
    mxCellEditor.prototype.resize = function (state, trigger) {
      if (this.textarea != null) {
        var state = this.graph.getView().getState(this.editingCell);

        if (this.codeViewMode && state != null) {
          var scale = state.view.scale;
          this.bounds = mxRectangle.fromRectangle(state);

          // General placement of code editor if cell has no size
          // LATER: Fix HTML editor bounds for edge labels
          if (this.bounds.width == 0 && this.bounds.height == 0) {
            this.bounds.width = 160 * scale;
            this.bounds.height = 60 * scale;

            var m = state.text != null ? state.text.margin : null;

            if (m == null) {
              m = mxUtils.getAlignmentAsPoint(
                mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER),
                mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE)
              );
            }

            this.bounds.x += m.x * this.bounds.width;
            this.bounds.y += m.y * this.bounds.height;
          }

          this.textarea.style.width = Math.round((this.bounds.width - 4) / scale) + 'px';
          this.textarea.style.height = Math.round((this.bounds.height - 4) / scale) + 'px';
          this.textarea.style.overflow = 'auto';

          // Adds scrollbar offset if visible
          if (this.textarea.clientHeight < this.textarea.offsetHeight) {
            this.textarea.style.height =
              Math.round(this.bounds.height / scale) + (this.textarea.offsetHeight - this.textarea.clientHeight) + 'px';
            this.bounds.height = parseInt(this.textarea.style.height) * scale;
          }

          if (this.textarea.clientWidth < this.textarea.offsetWidth) {
            this.textarea.style.width =
              Math.round(this.bounds.width / scale) + (this.textarea.offsetWidth - this.textarea.clientWidth) + 'px';
            this.bounds.width = parseInt(this.textarea.style.width) * scale;
          }

          this.textarea.style.left = Math.round(this.bounds.x) + 'px';
          this.textarea.style.top = Math.round(this.bounds.y) + 'px';

          if (mxClient.IS_VML) {
            this.textarea.style.zoom = scale;
          } else {
            mxUtils.setPrefixedStyle(this.textarea.style, 'transform', 'scale(' + scale + ',' + scale + ')');
          }
        } else {
          this.textarea.style.height = '';
          this.textarea.style.overflow = '';
          mxCellEditorResize.apply(this, arguments);
        }
      }
    };

    mxCellEditorGetInitialValue = mxCellEditor.prototype.getInitialValue;
    mxCellEditor.prototype.getInitialValue = function (state, trigger) {
      if (mxUtils.getValue(state.style, 'html', '0') == '0') {
        return mxCellEditorGetInitialValue.apply(this, arguments);
      } else {
        var result = this.graph.getEditingValue(state.cell, trigger);

        if (mxUtils.getValue(state.style, 'nl2Br', '1') == '1') {
          result = result.replace(/\n/g, '<br/>');
        }

        result = this.graph.sanitizeHtml(result, true);

        return result;
      }
    };

    mxCellEditorGetCurrentValue = mxCellEditor.prototype.getCurrentValue;
    mxCellEditor.prototype.getCurrentValue = function (state) {
      if (mxUtils.getValue(state.style, 'html', '0') == '0') {
        return mxCellEditorGetCurrentValue.apply(this, arguments);
      } else {
        var result = this.graph.sanitizeHtml(this.textarea.innerHTML, true);

        if (mxUtils.getValue(state.style, 'nl2Br', '1') == '1') {
          result = result.replace(/\r\n/g, '<br/>').replace(/\n/g, '<br/>');
        } else {
          result = result.replace(/\r\n/g, '').replace(/\n/g, '');
        }

        return result;
      }
    };

    var mxCellEditorStopEditing = mxCellEditor.prototype.stopEditing;
    mxCellEditor.prototype.stopEditing = function (cancel) {
      // Restores default view mode before applying value
      if (this.codeViewMode) {
        this.toggleViewMode();
      }

      mxCellEditorStopEditing.apply(this, arguments);

      // Tries to move focus back to container after editing if possible
      this.focusContainer();
    };

    mxCellEditor.prototype.focusContainer = function () {
      try {
        this.graph.container.focus();
      } catch (e) {
        // ignore
      }
    };

    var mxCellEditorApplyValue = mxCellEditor.prototype.applyValue;
    mxCellEditor.prototype.applyValue = function (state, value) {
      // Removes empty relative child labels in edges
      this.graph.getModel().beginUpdate();

      try {
        mxCellEditorApplyValue.apply(this, arguments);

        if (
          value == '' &&
          this.graph.isCellDeletable(state.cell) &&
          this.graph.model.getChildCount(state.cell) == 0 &&
          this.graph.isTransparentState(state)
        ) {
          this.graph.removeCells([state.cell], false);
        }
      } finally {
        this.graph.getModel().endUpdate();
      }
    };

    /**
     * Returns the background color to be used for the editing box. This returns
     * the label background for edge labels and null for all other cases.
     */
    mxCellEditor.prototype.getBackgroundColor = function (state) {
      var color = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, null);

      if (
        (color == null || color == mxConstants.NONE) &&
        state.cell.geometry != null &&
        state.cell.geometry.width > 0 &&
        (mxUtils.getValue(state.style, mxConstants.STYLE_ROTATION, 0) != 0 ||
          mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 0)
      ) {
        color = mxUtils.getValue(state.style, mxConstants.STYLE_FILLCOLOR, null);
      }

      if (color == mxConstants.NONE) {
        color = null;
      }

      return color;
    };

    mxCellEditor.prototype.getMinimumSize = function (state) {
      var scale = this.graph.getView().scale;

      return new mxRectangle(0, 0, state.text == null ? 30 : state.text.size * scale + 20, 30);
    };

    /**
     * Hold Alt to ignore drop target.
     */
    mxGraphHandlerIsValidDropTarget = mxGraphHandler.prototype.isValidDropTarget;
    mxGraphHandler.prototype.isValidDropTarget = function (target, me) {
      return mxGraphHandlerIsValidDropTarget.apply(this, arguments) && !mxEvent.isAltDown(me.getEvent);
    };

    /**
     * Hints on handlers
     */
    function createHint() {
      var hint = document.createElement('div');
      hint.className = 'geHint';
      hint.style.whiteSpace = 'nowrap';
      hint.style.position = 'absolute';

      return hint;
    }

    /**
     * Format pixels in the given unit
     */
    function formatHintText(pixels, unit) {
      switch (unit) {
        case mxConstants.POINTS:
          return pixels;
        case mxConstants.MILLIMETERS:
          return (pixels / mxConstants.PIXELS_PER_MM).toFixed(1);
        case mxConstants.INCHES:
          return (pixels / mxConstants.PIXELS_PER_INCH).toFixed(2);
      }
    }

    mxGraphView.prototype.formatUnitText = function (pixels) {
      return pixels ? formatHintText(pixels, this.unit) : pixels;
    };

    /**
     * Updates the hint for the current operation.
     */
    mxGraphHandler.prototype.updateHint = function (me) {
      if (this.pBounds != null && (this.shape != null || this.livePreviewActive)) {
        if (this.hint == null) {
          this.hint = createHint();
          this.graph.container.appendChild(this.hint);
        }

        var t = this.graph.view.translate;
        var s = this.graph.view.scale;
        var x = this.roundLength((this.bounds.x + this.currentDx) / s - t.x);
        var y = this.roundLength((this.bounds.y + this.currentDy) / s - t.y);
        var unit = this.graph.view.unit;

        this.hint.innerHTML = formatHintText(x, unit) + ', ' + formatHintText(y, unit);

        this.hint.style.left =
          this.pBounds.x + this.currentDx + Math.round((this.pBounds.width - this.hint.clientWidth) / 2) + 'px';
        this.hint.style.top = this.pBounds.y + this.currentDy + this.pBounds.height + Editor.hintOffset + 'px';
      }
    };

    /**
     * Updates the hint for the current operation.
     */
    mxGraphHandler.prototype.removeHint = function () {
      if (this.hint != null) {
        this.hint.parentNode.removeChild(this.hint);
        this.hint = null;
      }
    };

    /**
     * Overridden to allow for shrinking pools when lanes are resized.
     */
    var stackLayoutResizeCell = mxStackLayout.prototype.resizeCell;
    mxStackLayout.prototype.resizeCell = function (cell, bounds) {
      stackLayoutResizeCell.apply(this, arguments);
      var style = this.graph.getCellStyle(cell);

      if (style['childLayout'] == null) {
        var parent = this.graph.model.getParent(cell);
        var geo = parent != null ? this.graph.getCellGeometry(parent) : null;

        if (geo != null) {
          style = this.graph.getCellStyle(parent);

          if (style['childLayout'] == 'stackLayout') {
            var border = parseFloat(mxUtils.getValue(style, 'stackBorder', mxStackLayout.prototype.border));
            var horizontal = mxUtils.getValue(style, 'horizontalStack', '1') == '1';
            var start = this.graph.getActualStartSize(parent);
            geo = geo.clone();

            if (horizontal) {
              geo.height = bounds.height + start.y + start.height + 2 * border;
            } else {
              geo.width = bounds.width + start.x + start.width + 2 * border;
            }

            this.graph.model.setGeometry(parent, geo);
          }
        }
      }
    };

    /**
     * Shows handle for table instead of rows and cells.
     */
    var selectionCellsHandlerGetHandledSelectionCells = mxSelectionCellsHandler.prototype.getHandledSelectionCells;
    mxSelectionCellsHandler.prototype.getHandledSelectionCells = function () {
      var cells = selectionCellsHandlerGetHandledSelectionCells.apply(this, arguments);
      var dict = new mxDictionary();
      var model = this.graph.model;
      var result = [];

      function addCell(cell) {
        if (!dict.get(cell)) {
          dict.put(cell, true);
          result.push(cell);
        }
      }

      for (var i = 0; i < cells.length; i++) {
        var cell = cells[i];

        if (this.graph.isTableCell(cell)) {
          addCell(model.getParent(model.getParent(cell)));
        } else if (this.graph.isTableRow(cell)) {
          addCell(model.getParent(cell));
        }

        addCell(cell);
      }

      return result;
    };

    /**
     * Creates the shape used to draw the selection border.
     */
    var vertexHandlerCreateParentHighlightShape = mxVertexHandler.prototype.createParentHighlightShape;
    mxVertexHandler.prototype.createParentHighlightShape = function (bounds) {
      var shape = vertexHandlerCreateParentHighlightShape.apply(this, arguments);

      shape.stroke = '#C0C0C0';
      shape.strokewidth = 1;

      return shape;
    };

    /**
     * Creates the shape used to draw the selection border.
     */
    var edgeHandlerCreateParentHighlightShape = mxEdgeHandler.prototype.createParentHighlightShape;
    mxEdgeHandler.prototype.createParentHighlightShape = function (bounds) {
      var shape = edgeHandlerCreateParentHighlightShape.apply(this, arguments);

      shape.stroke = '#C0C0C0';
      shape.strokewidth = 1;

      return shape;
    };

    /**
     * Moves rotation handle to top, right corner.
     */
    mxVertexHandler.prototype.rotationHandleVSpacing = -12;
    mxVertexHandler.prototype.getRotationHandlePosition = function () {
      var padding = this.getHandlePadding();

      return new mxPoint(
        this.bounds.x + this.bounds.width - this.rotationHandleVSpacing + padding.x / 2,
        this.bounds.y + this.rotationHandleVSpacing - padding.y / 2
      );
    };

    /**
     * Enables recursive resize for groups.
     */
    mxVertexHandler.prototype.isRecursiveResize = function (state, me) {
      return this.graph.isRecursiveVertexResize(state) && !mxEvent.isControlDown(me.getEvent());
    };

    /**
     * Enables centered resize events.
     */
    mxVertexHandler.prototype.isCenteredEvent = function (state, me) {
      return (
        (!(
          !this.graph.isSwimlane(state.cell) &&
          this.graph.model.getChildCount(state.cell) > 0 &&
          !this.graph.isCellCollapsed(state.cell) &&
          mxUtils.getValue(state.style, 'recursiveResize', '1') == '1' &&
          mxUtils.getValue(state.style, 'childLayout', null) == null
        ) &&
          mxEvent.isControlDown(me.getEvent())) ||
        mxEvent.isMetaDown(me.getEvent())
      );
    };

    /**
     * Hides rotation handle for table cells and rows.
     */
    var vertexHandlerIsRotationHandleVisible = mxVertexHandler.prototype.isRotationHandleVisible;
    mxVertexHandler.prototype.isRotationHandleVisible = function () {
      return (
        vertexHandlerIsRotationHandleVisible.apply(this, arguments) &&
        !this.graph.isTableCell(this.state.cell) &&
        !this.graph.isTableRow(this.state.cell) &&
        !this.graph.isTable(this.state.cell)
      );
    };

    /**
     * Hides rotation handle for table cells and rows.
     */
    mxVertexHandler.prototype.getSizerBounds = function () {
      if (this.graph.isTableCell(this.state.cell)) {
        return this.graph.view.getState(this.graph.model.getParent(this.graph.model.getParent(this.state.cell)));
      } else {
        return this.bounds;
      }
    };

    /**
     * Hides rotation handle for table cells and rows.
     */
    var vertexHandlerIsParentHighlightVisible = mxVertexHandler.prototype.isParentHighlightVisible;
    mxVertexHandler.prototype.isParentHighlightVisible = function () {
      return (
        vertexHandlerIsParentHighlightVisible.apply(this, arguments) &&
        !this.graph.isTableCell(this.state.cell) &&
        !this.graph.isTableRow(this.state.cell)
      );
    };

    /**
     * Hides rotation handle for table cells and rows.
     */
    var vertexHandlerIsCustomHandleVisible = mxVertexHandler.prototype.isCustomHandleVisible;
    mxVertexHandler.prototype.isCustomHandleVisible = function (handle) {
      return (
        handle.tableHandle ||
        (vertexHandlerIsCustomHandleVisible.apply(this, arguments) &&
          (!this.graph.isTable(this.state.cell) || this.graph.isCellSelected(this.state.cell)))
      );
    };

    /**
     * Adds selection border inset for table cells and rows.
     */
    mxVertexHandler.prototype.getSelectionBorderInset = function () {
      var result = 0;

      if (this.graph.isTableRow(this.state.cell)) {
        result = 1;
      } else if (this.graph.isTableCell(this.state.cell)) {
        result = 2;
      }

      return result;
    };

    /**
     * Adds custom handles for table cells.
     */
    var vertexHandlerGetSelectionBorderBounds = mxVertexHandler.prototype.getSelectionBorderBounds;
    mxVertexHandler.prototype.getSelectionBorderBounds = function () {
      return vertexHandlerGetSelectionBorderBounds.apply(this, arguments).grow(-this.getSelectionBorderInset());
    };

    /**
     * Adds custom handles for table cells.
     */
    var vertexHandlerCreateCustomHandles = mxVertexHandler.prototype.createCustomHandles;
    mxVertexHandler.prototype.createCustomHandles = function () {
      var handles = vertexHandlerCreateCustomHandles.apply(this, arguments);

      if (this.graph.isTable(this.state.cell)) {
        var graph = this.graph;
        var model = graph.model;
        var tableState = this.state;
        var sel = this.selectionBorder;
        var self = this;

        if (handles == null) {
          handles = [];
        }

        // Adds handles for rows and columns
        var rows = graph.view.getCellStates(model.getChildCells(this.state.cell, true));

        if (rows.length > 0) {
          var cols = graph.view.getCellStates(model.getChildCells(rows[0].cell, true));

          // Adds column width handles
          for (var i = 0; i < cols.length; i++) {
            mxUtils.bind(this, function (index) {
              var colState = cols[index];
              var nextCol = index < cols.length - 1 ? cols[index + 1] : null;

              var shape = new mxLine(new mxRectangle(), mxConstants.NONE, 1, true);
              shape.isDashed = sel.isDashed;

              // Workaround for event handling on overlapping cells with tolerance
              shape.svgStrokeTolerance++;

              var handle = new mxHandle(colState, 'col-resize', null, shape);
              handle.tableHandle = true;
              var dx = 0;

              handle.shape.node.parentNode.insertBefore(handle.shape.node, handle.shape.node.parentNode.firstChild);

              handle.redraw = function () {
                if (this.shape != null && this.state.shape != null) {
                  var start = graph.getActualStartSize(tableState.cell);
                  this.shape.stroke = dx == 0 ? mxConstants.NONE : sel.stroke;
                  this.shape.bounds.x = this.state.x + this.state.width + dx * this.graph.view.scale;
                  this.shape.bounds.width = 1;
                  this.shape.bounds.y = tableState.y + (index == cols.length - 1 ? 0 : start.y * this.graph.view.scale);
                  this.shape.bounds.height =
                    tableState.height -
                    (index == cols.length - 1 ? 0 : (start.height + start.y) * this.graph.view.scale);
                  this.shape.redraw();
                }
              };

              var shiftPressed = false;

              handle.setPosition = function (bounds, pt, me) {
                dx = Math.max(Graph.minTableColumnWidth - bounds.width, pt.x - bounds.x - bounds.width);
                shiftPressed = mxEvent.isShiftDown(me.getEvent());

                if (nextCol != null && !shiftPressed) {
                  dx = Math.min(
                    (nextCol.x + nextCol.width - colState.x - colState.width) / graph.view.scale -
                      Graph.minTableColumnWidth,
                    dx
                  );
                }
              };

              handle.execute = function (me) {
                if (dx != 0) {
                  graph.setTableColumnWidth(this.state.cell, dx, shiftPressed);
                } else if (!self.blockDelayedSelection) {
                  var temp = graph.getCellAt(me.getGraphX(), me.getGraphY()) || tableState.cell;
                  graph.graphHandler.selectCellForEvent(temp, me);
                }

                dx = 0;
              };

              handle.reset = function () {
                dx = 0;
              };

              handles.push(handle);
            })(i);
          }

          // Adds row height handles
          for (var i = 0; i < rows.length; i++) {
            mxUtils.bind(this, function (index) {
              var rowState = rows[index];

              var shape = new mxLine(new mxRectangle(), mxConstants.NONE, 1);
              shape.isDashed = sel.isDashed;
              shape.svgStrokeTolerance++;

              var handle = new mxHandle(rowState, 'row-resize', null, shape);
              handle.tableHandle = true;
              var dy = 0;

              handle.shape.node.parentNode.insertBefore(handle.shape.node, handle.shape.node.parentNode.firstChild);

              handle.redraw = function () {
                if (this.shape != null && this.state.shape != null) {
                  this.shape.stroke = dy == 0 ? mxConstants.NONE : sel.stroke;
                  this.shape.bounds.x = this.state.x;
                  this.shape.bounds.width = this.state.width;
                  this.shape.bounds.y = this.state.y + this.state.height + dy * this.graph.view.scale;
                  this.shape.bounds.height = 1;
                  this.shape.redraw();
                }
              };

              handle.setPosition = function (bounds, pt, me) {
                dy = Math.max(Graph.minTableRowHeight - bounds.height, pt.y - bounds.y - bounds.height);
              };

              handle.execute = function (me) {
                if (dy != 0) {
                  graph.setTableRowHeight(this.state.cell, dy, !mxEvent.isShiftDown(me.getEvent()));
                } else if (!self.blockDelayedSelection) {
                  var temp = graph.getCellAt(me.getGraphX(), me.getGraphY()) || tableState.cell;
                  graph.graphHandler.selectCellForEvent(temp, me);
                }

                dy = 0;
              };

              handle.reset = function () {
                dy = 0;
              };

              handles.push(handle);
            })(i);
          }
        }
      }

      // Reserve gives point handles precedence over line handles
      return handles != null ? handles.reverse() : null;
    };

    var vertexHandlerSetHandlesVisible = mxVertexHandler.prototype.setHandlesVisible;

    mxVertexHandler.prototype.setHandlesVisible = function (visible) {
      vertexHandlerSetHandlesVisible.apply(this, arguments);

      if (this.moveHandles != null) {
        for (var i = 0; i < this.moveHandles.length; i++) {
          this.moveHandles[i].style.visibility = visible ? '' : 'hidden';
        }
      }

      if (this.cornerHandles != null) {
        for (var i = 0; i < this.cornerHandles.length; i++) {
          this.cornerHandles[i].node.style.visibility = visible ? '' : 'hidden';
        }
      }
    };

    /**
     * Creates or updates special handles for moving rows.
     */
    mxVertexHandler.prototype.refreshMoveHandles = function () {
      var graph = this.graph;
      var model = graph.model;

      // Destroys existing handles
      if (this.moveHandles != null) {
        for (var i = 0; i < this.moveHandles.length; i++) {
          this.moveHandles[i].parentNode.removeChild(this.moveHandles[i]);
        }

        this.moveHandles = null;
      }

      // Creates new handles
      this.moveHandles = [];

      for (var i = 0; i < model.getChildCount(this.state.cell); i++) {
        mxUtils.bind(this, function (rowState) {
          if (rowState != null && model.isVertex(rowState.cell)) {
            // Adds handle to move row
            // LATER: Move to overlay pane to hide during zoom but keep padding
            var moveHandle = mxUtils.createImage(Editor.rowMoveImage);
            moveHandle.style.position = 'absolute';
            moveHandle.style.cursor = 'pointer';
            moveHandle.style.width = '7px';
            moveHandle.style.height = '4px';
            moveHandle.style.padding = '4px 2px 4px 2px';
            moveHandle.rowState = rowState;

            mxEvent.addGestureListeners(
              moveHandle,
              mxUtils.bind(this, function (evt) {
                this.graph.popupMenuHandler.hideMenu();
                this.graph.stopEditing(false);

                if (this.graph.isToggleEvent(evt) || !this.graph.isCellSelected(rowState.cell)) {
                  this.graph.selectCellForEvent(rowState.cell, evt);
                }

                if (!mxEvent.isPopupTrigger(evt)) {
                  this.graph.graphHandler.start(
                    this.state.cell,
                    mxEvent.getClientX(evt),
                    mxEvent.getClientY(evt),
                    this.graph.getSelectionCells()
                  );
                  this.graph.graphHandler.cellWasClicked = true;
                  this.graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
                  this.graph.isMouseDown = true;
                }

                mxEvent.consume(evt);
              }),
              null,
              mxUtils.bind(this, function (evt) {
                if (mxEvent.isPopupTrigger(evt)) {
                  this.graph.popupMenuHandler.popup(
                    mxEvent.getClientX(evt),
                    mxEvent.getClientY(evt),
                    rowState.cell,
                    evt
                  );
                  mxEvent.consume(evt);
                }
              })
            );

            this.moveHandles.push(moveHandle);
            this.graph.container.appendChild(moveHandle);
          }
        })(this.graph.view.getState(model.getChildAt(this.state.cell, i)));
      }
    };

    /**
     * Adds handle padding for editing cells and exceptions.
     */
    mxVertexHandler.prototype.refresh = function () {
      if (this.customHandles != null) {
        for (var i = 0; i < this.customHandles.length; i++) {
          this.customHandles[i].destroy();
        }

        this.customHandles = this.createCustomHandles();
      }

      if (this.graph.isTable(this.state.cell)) {
        this.refreshMoveHandles();
      }
    };

    /**
     * Adds handle padding for editing cells and exceptions.
     */
    var vertexHandlerGetHandlePadding = mxVertexHandler.prototype.getHandlePadding;
    mxVertexHandler.prototype.getHandlePadding = function () {
      var result = new mxPoint(0, 0);
      var tol = this.tolerance;
      var name = this.state.style['shape'];

      if (mxCellRenderer.defaultShapes[name] == null && mxStencilRegistry.getStencil(name) == null) {
        name = mxConstants.SHAPE_RECTANGLE;
      }

      // Checks if custom handles are overlapping with the shape border
      var handlePadding =
        this.graph.isTable(this.state.cell) || this.graph.cellEditor.getEditingCell() == this.state.cell;

      if (!handlePadding) {
        if (this.customHandles != null) {
          for (var i = 0; i < this.customHandles.length; i++) {
            if (this.customHandles[i].shape != null && this.customHandles[i].shape.bounds != null) {
              var b = this.customHandles[i].shape.bounds;
              var px = b.getCenterX();
              var py = b.getCenterY();

              if (
                Math.abs(this.state.x - px) < b.width / 2 ||
                Math.abs(this.state.y - py) < b.height / 2 ||
                Math.abs(this.state.x + this.state.width - px) < b.width / 2 ||
                Math.abs(this.state.y + this.state.height - py) < b.height / 2
              ) {
                handlePadding = true;
                break;
              }
            }
          }
        }
      }

      if (handlePadding && this.sizers != null && this.sizers.length > 0 && this.sizers[0] != null) {
        tol /= 2;

        // Makes room for row move handle
        if (this.graph.isTable(this.state.cell)) {
          tol += 7;
        }

        result.x = this.sizers[0].bounds.width + tol;
        result.y = this.sizers[0].bounds.height + tol;
      } else {
        result = vertexHandlerGetHandlePadding.apply(this, arguments);
      }

      return result;
    };

    /**
     * Updates the hint for the current operation.
     */
    mxVertexHandler.prototype.updateHint = function (me) {
      if (this.index != mxEvent.LABEL_HANDLE) {
        if (this.hint == null) {
          this.hint = createHint();
          this.state.view.graph.container.appendChild(this.hint);
        }

        if (this.index == mxEvent.ROTATION_HANDLE) {
          this.hint.innerHTML = this.currentAlpha + '&deg;';
        } else {
          var s = this.state.view.scale;
          var unit = this.state.view.unit;
          this.hint.innerHTML =
            formatHintText(this.roundLength(this.bounds.width / s), unit) +
            ' x ' +
            formatHintText(this.roundLength(this.bounds.height / s), unit);
        }

        var rot = this.currentAlpha != null ? this.currentAlpha : this.state.style[mxConstants.STYLE_ROTATION] || '0';
        var bb = mxUtils.getBoundingBox(this.bounds, rot);

        if (bb == null) {
          bb = this.bounds;
        }

        this.hint.style.left = bb.x + Math.round((bb.width - this.hint.clientWidth) / 2) + 'px';
        this.hint.style.top = bb.y + bb.height + Editor.hintOffset + 'px';

        if (this.linkHint != null) {
          this.linkHint.style.display = 'none';
        }
      }
    };

    /**
     * Updates the hint for the current operation.
     */
    mxVertexHandler.prototype.removeHint = function () {
      mxGraphHandler.prototype.removeHint.apply(this, arguments);

      if (this.linkHint != null) {
        this.linkHint.style.display = '';
      }
    };

    /**
     * Hides link hint while moving cells.
     */
    var edgeHandlerMouseMove = mxEdgeHandler.prototype.mouseMove;

    mxEdgeHandler.prototype.mouseMove = function (sender, me) {
      edgeHandlerMouseMove.apply(this, arguments);

      if (
        this.linkHint != null &&
        this.linkHint.style.display != 'none' &&
        this.graph.graphHandler != null &&
        this.graph.graphHandler.first != null
      ) {
        this.linkHint.style.display = 'none';
      }
    };

    /**
     * Hides link hint while moving cells.
     */
    var edgeHandlerMouseUp = mxEdgeHandler.prototype.mouseUp;

    mxEdgeHandler.prototype.mouseUp = function (sender, me) {
      edgeHandlerMouseUp.apply(this, arguments);

      if (this.linkHint != null && this.linkHint.style.display == 'none') {
        this.linkHint.style.display = '';
      }
    };

    /**
     * Updates the hint for the current operation.
     */
    mxEdgeHandler.prototype.updateHint = function (me, point) {
      if (this.hint == null) {
        this.hint = createHint();
        this.state.view.graph.container.appendChild(this.hint);
      }

      var t = this.graph.view.translate;
      var s = this.graph.view.scale;
      var x = this.roundLength(point.x / s - t.x);
      var y = this.roundLength(point.y / s - t.y);
      var unit = this.graph.view.unit;

      this.hint.innerHTML = formatHintText(x, unit) + ', ' + formatHintText(y, unit);
      this.hint.style.visibility = 'visible';

      if (this.isSource || this.isTarget) {
        if (this.constraintHandler.currentConstraint != null && this.constraintHandler.currentFocus != null) {
          var pt = this.constraintHandler.currentConstraint.point;
          this.hint.innerHTML = '[' + Math.round(pt.x * 100) + '%, ' + Math.round(pt.y * 100) + '%]';
        } else if (this.marker.hasValidState()) {
          this.hint.style.visibility = 'hidden';
        }
      }

      this.hint.style.left = Math.round(me.getGraphX() - this.hint.clientWidth / 2) + 'px';
      this.hint.style.top = Math.max(me.getGraphY(), point.y) + Editor.hintOffset + 'px';

      if (this.linkHint != null) {
        this.linkHint.style.display = 'none';
      }
    };

    /**
     * Updates the hint for the current operation.
     */
    mxEdgeHandler.prototype.removeHint = mxVertexHandler.prototype.removeHint;

    /**
     * Defines the handles for the UI. Uses data-URIs to speed-up loading time where supported.
     */
    // TODO: Increase handle padding
    HoverIcons.prototype.mainHandle = !mxClient.IS_SVG
      ? new mxImage(IMAGE_PATH + '/handle-main.png', 17, 17)
      : Graph.createSvgImage(
          18,
          18,
          '<circle cx="9" cy="9" r="5" stroke="#fff" fill="' + HoverIcons.prototype.arrowFill + '" stroke-width="1"/>'
        );
    HoverIcons.prototype.secondaryHandle = !mxClient.IS_SVG
      ? new mxImage(IMAGE_PATH + '/handle-secondary.png', 17, 17)
      : Graph.createSvgImage(16, 16, '<path d="m 8 3 L 13 8 L 8 13 L 3 8 z" stroke="#fff" fill="#fca000"/>');
    HoverIcons.prototype.fixedHandle = !mxClient.IS_SVG
      ? new mxImage(IMAGE_PATH + '/handle-fixed.png', 17, 17)
      : Graph.createSvgImage(
          18,
          18,
          '<circle cx="9" cy="9" r="5" stroke="#fff" fill="' +
            HoverIcons.prototype.arrowFill +
            '" stroke-width="1"/><path d="m 7 7 L 11 11 M 7 11 L 11 7" stroke="#fff"/>'
        );
    HoverIcons.prototype.terminalHandle = !mxClient.IS_SVG
      ? new mxImage(IMAGE_PATH + '/handle-terminal.png', 17, 17)
      : Graph.createSvgImage(
          18,
          18,
          '<circle cx="9" cy="9" r="5" stroke="#fff" fill="' +
            HoverIcons.prototype.arrowFill +
            '" stroke-width="1"/><circle cx="9" cy="9" r="2" stroke="#fff" fill="transparent"/>'
        );
    HoverIcons.prototype.rotationHandle = !mxClient.IS_SVG
      ? new mxImage(IMAGE_PATH + '/handle-rotate.png', 16, 16)
      : Graph.createSvgImage(
          16,
          16,
          '<path stroke="' +
            HoverIcons.prototype.arrowFill +
            '" fill="' +
            HoverIcons.prototype.arrowFill +
            '" d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"/>',
          24,
          24
        );

    if (mxClient.IS_SVG) {
      mxConstraintHandler.prototype.pointImage = Graph.createSvgImage(
        5,
        5,
        '<path d="m 0 0 L 5 5 M 0 5 L 5 0" stroke="' + HoverIcons.prototype.arrowFill + '"/>'
      );
    }

    mxVertexHandler.TABLE_HANDLE_COLOR = '#fca000';
    mxVertexHandler.prototype.handleImage = HoverIcons.prototype.mainHandle;
    mxVertexHandler.prototype.secondaryHandleImage = HoverIcons.prototype.secondaryHandle;
    mxEdgeHandler.prototype.handleImage = HoverIcons.prototype.mainHandle;
    mxEdgeHandler.prototype.terminalHandleImage = HoverIcons.prototype.terminalHandle;
    mxEdgeHandler.prototype.fixedHandleImage = HoverIcons.prototype.fixedHandle;
    mxEdgeHandler.prototype.labelHandleImage = HoverIcons.prototype.secondaryHandle;
    mxOutline.prototype.sizerImage = HoverIcons.prototype.mainHandle;

    if (window.Sidebar != null) {
      Sidebar.prototype.triangleUp = HoverIcons.prototype.triangleUp;
      Sidebar.prototype.triangleRight = HoverIcons.prototype.triangleRight;
      Sidebar.prototype.triangleDown = HoverIcons.prototype.triangleDown;
      Sidebar.prototype.triangleLeft = HoverIcons.prototype.triangleLeft;
      Sidebar.prototype.refreshTarget = HoverIcons.prototype.refreshTarget;
      Sidebar.prototype.roundDrop = HoverIcons.prototype.roundDrop;
    }

    // Pre-fetches images (only needed for non data-uris)
    if (!mxClient.IS_SVG) {
      new Image().src = HoverIcons.prototype.mainHandle.src;
      new Image().src = HoverIcons.prototype.fixedHandle.src;
      new Image().src = HoverIcons.prototype.terminalHandle.src;
      new Image().src = HoverIcons.prototype.secondaryHandle.src;
      new Image().src = HoverIcons.prototype.rotationHandle.src;

      new Image().src = HoverIcons.prototype.triangleUp.src;
      new Image().src = HoverIcons.prototype.triangleRight.src;
      new Image().src = HoverIcons.prototype.triangleDown.src;
      new Image().src = HoverIcons.prototype.triangleLeft.src;
      new Image().src = HoverIcons.prototype.refreshTarget.src;
      new Image().src = HoverIcons.prototype.roundDrop.src;
    }

    // Adds rotation handle and live preview
    mxVertexHandler.prototype.rotationEnabled = true;
    mxVertexHandler.prototype.manageSizers = true;
    mxVertexHandler.prototype.livePreview = true;
    mxGraphHandler.prototype.maxLivePreview = 16;

    // Increases default rubberband opacity (default is 20)
    mxRubberband.prototype.defaultOpacity = 30;

    // Enables connections along the outline, virtual waypoints, parent highlight etc
    mxConnectionHandler.prototype.outlineConnect = true;
    mxCellHighlight.prototype.keepOnTop = true;
    mxVertexHandler.prototype.parentHighlightEnabled = true;

    mxEdgeHandler.prototype.parentHighlightEnabled = true;
    mxEdgeHandler.prototype.dblClickRemoveEnabled = true;
    mxEdgeHandler.prototype.straightRemoveEnabled = true;
    mxEdgeHandler.prototype.virtualBendsEnabled = true;
    mxEdgeHandler.prototype.mergeRemoveEnabled = true;
    mxEdgeHandler.prototype.manageLabelHandle = true;
    mxEdgeHandler.prototype.outlineConnect = true;

    // Disables adding waypoints if shift is pressed
    mxEdgeHandler.prototype.isAddVirtualBendEvent = function (me) {
      return !mxEvent.isShiftDown(me.getEvent());
    };

    // Disables custom handles if shift is pressed
    mxEdgeHandler.prototype.isCustomHandleEvent = function (me) {
      return !mxEvent.isShiftDown(me.getEvent());
    };

    /**
     * Implements touch style
     */
    if (Graph.touchStyle) {
      // Larger tolerance for real touch devices
      if (mxClient.IS_TOUCH || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) {
        mxShape.prototype.svgStrokeTolerance = 18;
        mxVertexHandler.prototype.tolerance = 12;
        mxEdgeHandler.prototype.tolerance = 12;
        Graph.prototype.tolerance = 12;

        mxVertexHandler.prototype.rotationHandleVSpacing = -16;

        // Implements a smaller tolerance for mouse events and a larger tolerance for touch
        // events on touch devices. The default tolerance (4px) is used for mouse events.
        mxConstraintHandler.prototype.getTolerance = function (me) {
          return mxEvent.isMouseEvent(me.getEvent()) ? 4 : this.graph.getTolerance();
        };
      }

      // One finger pans (no rubberband selection) must start regardless of mouse button
      mxPanningHandler.prototype.isPanningTrigger = function (me) {
        var evt = me.getEvent();

        return (
          (me.getState() == null && !mxEvent.isMouseEvent(evt)) ||
          (mxEvent.isPopupTrigger(evt) &&
            (me.getState() == null || mxEvent.isControlDown(evt) || mxEvent.isShiftDown(evt)))
        );
      };

      // Don't clear selection if multiple cells selected
      var graphHandlerMouseDown = mxGraphHandler.prototype.mouseDown;
      mxGraphHandler.prototype.mouseDown = function (sender, me) {
        graphHandlerMouseDown.apply(this, arguments);

        if (
          mxEvent.isTouchEvent(me.getEvent()) &&
          this.graph.isCellSelected(me.getCell()) &&
          this.graph.getSelectionCount() > 1
        ) {
          this.delayedSelection = false;
        }
      };
    } else {
      // Removes ctrl+shift as panning trigger for space splitting
      mxPanningHandler.prototype.isPanningTrigger = function (me) {
        var evt = me.getEvent();

        return (
          (mxEvent.isLeftMouseButton(evt) &&
            ((this.useLeftButtonForPanning && me.getState() == null) ||
              (mxEvent.isControlDown(evt) && !mxEvent.isShiftDown(evt)))) ||
          (this.usePopupTrigger && mxEvent.isPopupTrigger(evt))
        );
      };
    }

    // Overrides/extends rubberband for space handling with Ctrl+Shift(+Alt) drag ("scissors tool")
    mxRubberband.prototype.isSpaceEvent = function (me) {
      return (
        this.graph.isEnabled() &&
        !this.graph.isCellLocked(this.graph.getDefaultParent()) &&
        mxEvent.isControlDown(me.getEvent()) &&
        mxEvent.isShiftDown(me.getEvent())
      );
    };

    // Cancelled state
    mxRubberband.prototype.cancelled = false;

    // Cancels ongoing rubberband selection but consumed event to avoid reset of selection
    mxRubberband.prototype.cancel = function () {
      if (this.isActive()) {
        this.cancelled = true;
        this.reset();
      }
    };

    // Handles moving of cells in both half panes
    mxRubberband.prototype.mouseUp = function (sender, me) {
      if (this.cancelled) {
        this.cancelled = false;
        me.consume();
      } else {
        var execute = this.div != null && this.div.style.display != 'none';

        var x0 = null;
        var y0 = null;
        var dx = null;
        var dy = null;

        if (this.first != null && this.currentX != null && this.currentY != null) {
          x0 = this.first.x;
          y0 = this.first.y;
          dx = (this.currentX - x0) / this.graph.view.scale;
          dy = (this.currentY - y0) / this.graph.view.scale;

          if (!mxEvent.isAltDown(me.getEvent())) {
            dx = this.graph.snap(dx);
            dy = this.graph.snap(dy);

            if (!this.graph.isGridEnabled()) {
              if (Math.abs(dx) < this.graph.tolerance) {
                dx = 0;
              }

              if (Math.abs(dy) < this.graph.tolerance) {
                dy = 0;
              }
            }
          }
        }

        this.reset();

        if (execute) {
          if (mxEvent.isAltDown(me.getEvent()) && this.graph.isToggleEvent(me.getEvent())) {
            var rect = new mxRectangle(this.x, this.y, this.width, this.height);
            var cells = this.graph.getCells(rect.x, rect.y, rect.width, rect.height);

            this.graph.removeSelectionCells(cells);
          } else if (this.isSpaceEvent(me)) {
            this.graph.model.beginUpdate();
            try {
              var cells = this.graph.getCellsBeyond(x0, y0, this.graph.getDefaultParent(), true, true);

              for (var i = 0; i < cells.length; i++) {
                if (this.graph.isCellMovable(cells[i])) {
                  var tmp = this.graph.view.getState(cells[i]);
                  var geo = this.graph.getCellGeometry(cells[i]);

                  if (tmp != null && geo != null) {
                    geo = geo.clone();
                    geo.translate(dx, dy);
                    this.graph.model.setGeometry(cells[i], geo);
                  }
                }
              }
            } finally {
              this.graph.model.endUpdate();
            }
          } else {
            var rect = new mxRectangle(this.x, this.y, this.width, this.height);
            this.graph.selectRegion(rect, me.getEvent());
          }

          me.consume();
        }
      }
    };

    // Handles preview for creating/removing space in diagram
    mxRubberband.prototype.mouseMove = function (sender, me) {
      if (!me.isConsumed() && this.first != null) {
        var origin = mxUtils.getScrollOrigin(this.graph.container);
        var offset = mxUtils.getOffset(this.graph.container);
        origin.x -= offset.x;
        origin.y -= offset.y;
        var x = me.getX() + origin.x;
        var y = me.getY() + origin.y;
        var dx = this.first.x - x;
        var dy = this.first.y - y;
        var tol = this.graph.tolerance;

        if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol) {
          if (this.div == null) {
            this.div = this.createShape();
          }

          // Clears selection while rubberbanding. This is required because
          // the event is not consumed in mouseDown.
          mxUtils.clearSelection();
          this.update(x, y);

          if (this.isSpaceEvent(me)) {
            var right = this.x + this.width;
            var bottom = this.y + this.height;
            var scale = this.graph.view.scale;

            if (!mxEvent.isAltDown(me.getEvent())) {
              this.width = this.graph.snap(this.width / scale) * scale;
              this.height = this.graph.snap(this.height / scale) * scale;

              if (!this.graph.isGridEnabled()) {
                if (this.width < this.graph.tolerance) {
                  this.width = 0;
                }

                if (this.height < this.graph.tolerance) {
                  this.height = 0;
                }
              }

              if (this.x < this.first.x) {
                this.x = right - this.width;
              }

              if (this.y < this.first.y) {
                this.y = bottom - this.height;
              }
            }

            this.div.style.borderStyle = 'dashed';
            this.div.style.backgroundColor = 'white';
            this.div.style.left = this.x + 'px';
            this.div.style.top = this.y + 'px';
            this.div.style.width = Math.max(0, this.width) + 'px';
            this.div.style.height = this.graph.container.clientHeight + 'px';
            this.div.style.borderWidth = this.width <= 0 ? '0px 1px 0px 0px' : '0px 1px 0px 1px';

            if (this.secondDiv == null) {
              this.secondDiv = this.div.cloneNode(true);
              this.div.parentNode.appendChild(this.secondDiv);
            }

            this.secondDiv.style.left = this.x + 'px';
            this.secondDiv.style.top = this.y + 'px';
            this.secondDiv.style.width = this.graph.container.clientWidth + 'px';
            this.secondDiv.style.height = Math.max(0, this.height) + 'px';
            this.secondDiv.style.borderWidth = this.height <= 0 ? '1px 0px 0px 0px' : '1px 0px 1px 0px';
          } else {
            // Hides second div and restores style
            this.div.style.backgroundColor = '';
            this.div.style.borderWidth = '';
            this.div.style.borderStyle = '';

            if (this.secondDiv != null) {
              this.secondDiv.parentNode.removeChild(this.secondDiv);
              this.secondDiv = null;
            }
          }

          me.consume();
        }
      }
    };

    // Removes preview
    var mxRubberbandReset = mxRubberband.prototype.reset;
    mxRubberband.prototype.reset = function () {
      if (this.secondDiv != null) {
        this.secondDiv.parentNode.removeChild(this.secondDiv);
        this.secondDiv = null;
      }

      mxRubberbandReset.apply(this, arguments);
    };

    // Timer-based activation of outline connect in connection handler
    var startTime = new Date().getTime();
    var timeOnTarget = 0;

    var mxEdgeHandlerUpdatePreviewState = mxEdgeHandler.prototype.updatePreviewState;

    mxEdgeHandler.prototype.updatePreviewState = function (edge, point, terminalState, me) {
      mxEdgeHandlerUpdatePreviewState.apply(this, arguments);

      if (terminalState != this.currentTerminalState) {
        startTime = new Date().getTime();
        timeOnTarget = 0;
      } else {
        timeOnTarget = new Date().getTime() - startTime;
      }

      this.currentTerminalState = terminalState;
    };

    // Timer-based outline connect
    var mxEdgeHandlerIsOutlineConnectEvent = mxEdgeHandler.prototype.isOutlineConnectEvent;

    mxEdgeHandler.prototype.isOutlineConnectEvent = function (me) {
      return (
        (this.currentTerminalState != null && me.getState() == this.currentTerminalState && timeOnTarget > 2000) ||
        ((this.currentTerminalState == null ||
          mxUtils.getValue(this.currentTerminalState.style, 'outlineConnect', '1') != '0') &&
          mxEdgeHandlerIsOutlineConnectEvent.apply(this, arguments))
      );
    };

    // Shows secondary handle for fixed connection points
    mxEdgeHandler.prototype.createHandleShape = function (index, virtual) {
      var source = index != null && index == 0;
      var terminalState = this.state.getVisibleTerminalState(source);
      var c =
        index != null &&
        (index == 0 ||
          index >= this.state.absolutePoints.length - 1 ||
          (this.constructor == mxElbowEdgeHandler && index == 2))
          ? this.graph.getConnectionConstraint(this.state, terminalState, source)
          : null;
      var pt = c != null ? this.graph.getConnectionPoint(this.state.getVisibleTerminalState(source), c) : null;
      var img =
        pt != null
          ? this.fixedHandleImage
          : c != null && terminalState != null
          ? this.terminalHandleImage
          : this.handleImage;

      if (img != null) {
        var shape = new mxImageShape(new mxRectangle(0, 0, img.width, img.height), img.src);

        // Allows HTML rendering of the images
        shape.preserveImageAspect = false;

        return shape;
      } else {
        var s = mxConstants.HANDLE_SIZE;

        if (this.preferHtml) {
          s -= 1;
        }

        return new mxRectangleShape(
          new mxRectangle(0, 0, s, s),
          mxConstants.HANDLE_FILLCOLOR,
          mxConstants.HANDLE_STROKECOLOR
        );
      }
    };

    var vertexHandlerCreateSizerShape = mxVertexHandler.prototype.createSizerShape;
    mxVertexHandler.prototype.createSizerShape = function (bounds, index, fillColor) {
      this.handleImage =
        index == mxEvent.ROTATION_HANDLE
          ? HoverIcons.prototype.rotationHandle
          : index == mxEvent.LABEL_HANDLE
          ? this.secondaryHandleImage
          : this.handleImage;

      return vertexHandlerCreateSizerShape.apply(this, arguments);
    };

    // Special case for single edge label handle moving in which case the text bounding box is used
    var mxGraphHandlerGetBoundingBox = mxGraphHandler.prototype.getBoundingBox;
    mxGraphHandler.prototype.getBoundingBox = function (cells) {
      if (cells != null && cells.length == 1) {
        var model = this.graph.getModel();
        var parent = model.getParent(cells[0]);
        var geo = this.graph.getCellGeometry(cells[0]);

        if (model.isEdge(parent) && geo != null && geo.relative) {
          var state = this.graph.view.getState(cells[0]);

          if (
            state != null &&
            state.width < 2 &&
            state.height < 2 &&
            state.text != null &&
            state.text.boundingBox != null
          ) {
            return mxRectangle.fromRectangle(state.text.boundingBox);
          }
        }
      }

      return mxGraphHandlerGetBoundingBox.apply(this, arguments);
    };

    // Ignores child cells with part style as guides
    var mxGraphHandlerGetGuideStates = mxGraphHandler.prototype.getGuideStates;

    mxGraphHandler.prototype.getGuideStates = function () {
      var states = mxGraphHandlerGetGuideStates.apply(this, arguments);
      var result = [];

      // NOTE: Could do via isStateIgnored hook
      for (var i = 0; i < states.length; i++) {
        if (mxUtils.getValue(states[i].style, 'part', '0') != '1') {
          result.push(states[i]);
        }
      }

      return result;
    };

    // Uses text bounding box for edge labels
    var mxVertexHandlerGetSelectionBounds = mxVertexHandler.prototype.getSelectionBounds;
    mxVertexHandler.prototype.getSelectionBounds = function (state) {
      var model = this.graph.getModel();
      var parent = model.getParent(state.cell);
      var geo = this.graph.getCellGeometry(state.cell);

      if (
        model.isEdge(parent) &&
        geo != null &&
        geo.relative &&
        state.width < 2 &&
        state.height < 2 &&
        state.text != null &&
        state.text.boundingBox != null
      ) {
        var bbox = state.text.unrotatedBoundingBox || state.text.boundingBox;

        return new mxRectangle(Math.round(bbox.x), Math.round(bbox.y), Math.round(bbox.width), Math.round(bbox.height));
      } else {
        return mxVertexHandlerGetSelectionBounds.apply(this, arguments);
      }
    };

    // Redirects moving of edge labels to mxGraphHandler by not starting here.
    // This will use the move preview of mxGraphHandler (see above).
    var mxVertexHandlerMouseDown = mxVertexHandler.prototype.mouseDown;
    mxVertexHandler.prototype.mouseDown = function (sender, me) {
      var model = this.graph.getModel();
      var parent = model.getParent(this.state.cell);
      var geo = this.graph.getCellGeometry(this.state.cell);

      // Lets rotation events through
      var handle = this.getHandleForEvent(me);

      if (
        handle == mxEvent.ROTATION_HANDLE ||
        !model.isEdge(parent) ||
        geo == null ||
        !geo.relative ||
        this.state == null ||
        this.state.width >= 2 ||
        this.state.height >= 2
      ) {
        mxVertexHandlerMouseDown.apply(this, arguments);
      }
    };

    // Invokes turn on single click on rotation handle
    mxVertexHandler.prototype.rotateClick = function () {
      var stroke = mxUtils.getValue(this.state.style, mxConstants.STYLE_STROKECOLOR, mxConstants.NONE);
      var fill = mxUtils.getValue(this.state.style, mxConstants.STYLE_FILLCOLOR, mxConstants.NONE);

      if (
        this.state.view.graph.model.isVertex(this.state.cell) &&
        stroke == mxConstants.NONE &&
        fill == mxConstants.NONE
      ) {
        var angle = mxUtils.mod(mxUtils.getValue(this.state.style, mxConstants.STYLE_ROTATION, 0) + 90, 360);
        this.state.view.graph.setCellStyles(mxConstants.STYLE_ROTATION, angle, [this.state.cell]);
      } else {
        this.state.view.graph.turnShapes([this.state.cell]);
      }
    };

    var vertexHandlerMouseMove = mxVertexHandler.prototype.mouseMove;

    // Workaround for "isConsumed not defined" in MS Edge is to use arguments
    mxVertexHandler.prototype.mouseMove = function (sender, me) {
      vertexHandlerMouseMove.apply(this, arguments);

      if (this.graph.graphHandler.first != null) {
        if (this.rotationShape != null && this.rotationShape.node != null) {
          this.rotationShape.node.style.display = 'none';
        }

        if (this.linkHint != null && this.linkHint.style.display != 'none') {
          this.linkHint.style.display = 'none';
        }
      }
    };

    var vertexHandlerMouseUp = mxVertexHandler.prototype.mouseUp;

    mxVertexHandler.prototype.mouseUp = function (sender, me) {
      vertexHandlerMouseUp.apply(this, arguments);

      // Shows rotation handle only if one vertex is selected
      if (this.rotationShape != null && this.rotationShape.node != null) {
        this.rotationShape.node.style.display = this.graph.getSelectionCount() == 1 ? '' : 'none';
      }

      if (this.linkHint != null && this.linkHint.style.display == 'none') {
        this.linkHint.style.display = '';
      }

      // Resets state after gesture
      this.blockDelayedSelection = null;
    };

    var vertexHandlerInit = mxVertexHandler.prototype.init;
    mxVertexHandler.prototype.init = function () {
      vertexHandlerInit.apply(this, arguments);
      var redraw = false;

      if (this.rotationShape != null) {
        this.rotationShape.node.setAttribute('title', mxResources.get('rotateTooltip'));
      }

      if (this.graph.isTable(this.state.cell)) {
        this.refreshMoveHandles();
      }
      // Draws corner rectangles for single selected table cells and rows
      else if (
        this.graph.getSelectionCount() == 1 &&
        (this.graph.isTableCell(this.state.cell) || this.graph.isTableRow(this.state.cell))
      ) {
        this.cornerHandles = [];

        for (var i = 0; i < 4; i++) {
          var shape = new mxRectangleShape(new mxRectangle(0, 0, 6, 6), '#ffffff', mxConstants.HANDLE_STROKECOLOR);
          shape.dialect =
            this.graph.dialect != mxConstants.DIALECT_SVG ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
          shape.init(this.graph.view.getOverlayPane());
          this.cornerHandles.push(shape);
        }
      }

      var update = mxUtils.bind(this, function () {
        if (this.specialHandle != null) {
          this.specialHandle.node.style.display =
            this.graph.isEnabled() && this.graph.getSelectionCount() < this.graph.graphHandler.maxCells ? '' : 'none';
        }

        this.redrawHandles();
      });

      this.changeHandler = mxUtils.bind(this, function (sender, evt) {
        this.updateLinkHint(this.graph.getLinkForCell(this.state.cell), this.graph.getLinksForState(this.state));
        update();
      });

      this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.changeHandler);
      this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);

      // Repaint needed when editing stops and no change event is fired
      this.editingHandler = mxUtils.bind(this, function (sender, evt) {
        this.redrawHandles();
      });

      this.graph.addListener(mxEvent.EDITING_STOPPED, this.editingHandler);

      var link = this.graph.getLinkForCell(this.state.cell);
      var links = this.graph.getLinksForState(this.state);
      this.updateLinkHint(link, links);

      if (link != null || (links != null && links.length > 0)) {
        redraw = true;
      }

      if (redraw) {
        this.redrawHandles();
      }
    };

    mxVertexHandler.prototype.updateLinkHint = function (link, links) {
      try {
        if ((link == null && (links == null || links.length == 0)) || this.graph.getSelectionCount() > 1) {
          if (this.linkHint != null) {
            this.linkHint.parentNode.removeChild(this.linkHint);
            this.linkHint = null;
          }
        } else if (link != null || (links != null && links.length > 0)) {
          if (this.linkHint == null) {
            this.linkHint = createHint();
            this.linkHint.style.padding = '6px 8px 6px 8px';
            this.linkHint.style.opacity = '1';
            this.linkHint.style.filter = '';

            this.graph.container.appendChild(this.linkHint);
          }

          this.linkHint.innerHTML = '';

          if (link != null) {
            this.linkHint.appendChild(this.graph.createLinkForHint(link));

            if (this.graph.isEnabled() && typeof this.graph.editLink === 'function') {
              var changeLink = document.createElement('img');
              changeLink.setAttribute('src', Editor.editImage);
              changeLink.setAttribute('title', mxResources.get('editLink'));
              changeLink.setAttribute('width', '11');
              changeLink.setAttribute('height', '11');
              changeLink.style.marginLeft = '10px';
              changeLink.style.marginBottom = '-1px';
              changeLink.style.cursor = 'pointer';
              this.linkHint.appendChild(changeLink);

              mxEvent.addListener(
                changeLink,
                'click',
                mxUtils.bind(this, function (evt) {
                  this.graph.setSelectionCell(this.state.cell);
                  this.graph.editLink();
                  mxEvent.consume(evt);
                })
              );

              var removeLink = document.createElement('img');
              removeLink.setAttribute('src', Dialog.prototype.clearImage);
              removeLink.setAttribute('title', mxResources.get('removeIt', [mxResources.get('link')]));
              removeLink.setAttribute('width', '13');
              removeLink.setAttribute('height', '10');
              removeLink.style.marginLeft = '4px';
              removeLink.style.marginBottom = '-1px';
              removeLink.style.cursor = 'pointer';
              this.linkHint.appendChild(removeLink);

              mxEvent.addListener(
                removeLink,
                'click',
                mxUtils.bind(this, function (evt) {
                  this.graph.setLinkForCell(this.state.cell, null);
                  mxEvent.consume(evt);
                })
              );
            }
          }

          if (links != null) {
            for (var i = 0; i < links.length; i++) {
              var div = document.createElement('div');
              div.style.marginTop = link != null || i > 0 ? '6px' : '0px';
              div.appendChild(
                this.graph.createLinkForHint(links[i].getAttribute('href'), mxUtils.getTextContent(links[i]))
              );

              this.linkHint.appendChild(div);
            }
          }
        }
      } catch (e) {
        // ignore
      }
    };

    mxEdgeHandler.prototype.updateLinkHint = mxVertexHandler.prototype.updateLinkHint;

    // Creates special handles
    var edgeHandlerInit = mxEdgeHandler.prototype.init;
    mxEdgeHandler.prototype.init = function () {
      edgeHandlerInit.apply(this, arguments);

      // Disables connection points
      this.constraintHandler.isEnabled = mxUtils.bind(this, function () {
        return this.state.view.graph.connectionHandler.isEnabled();
      });

      var update = mxUtils.bind(this, function () {
        if (this.linkHint != null) {
          this.linkHint.style.display = this.graph.getSelectionCount() == 1 ? '' : 'none';
        }

        if (this.labelShape != null) {
          this.labelShape.node.style.display =
            this.graph.isEnabled() && this.graph.getSelectionCount() < this.graph.graphHandler.maxCells ? '' : 'none';
        }
      });

      this.changeHandler = mxUtils.bind(this, function (sender, evt) {
        this.updateLinkHint(this.graph.getLinkForCell(this.state.cell), this.graph.getLinksForState(this.state));
        update();
        this.redrawHandles();
      });

      this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.changeHandler);
      this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);

      var link = this.graph.getLinkForCell(this.state.cell);
      var links = this.graph.getLinksForState(this.state);

      if (link != null || (links != null && links.length > 0)) {
        this.updateLinkHint(link, links);
        this.redrawHandles();
      }
    };

    // Disables connection points
    var connectionHandlerInit = mxConnectionHandler.prototype.init;

    mxConnectionHandler.prototype.init = function () {
      connectionHandlerInit.apply(this, arguments);

      this.constraintHandler.isEnabled = mxUtils.bind(this, function () {
        return this.graph.connectionHandler.isEnabled();
      });
    };

    // Updates special handles
    var vertexHandlerRedrawHandles = mxVertexHandler.prototype.redrawHandles;
    mxVertexHandler.prototype.redrawHandles = function () {
      if (this.moveHandles != null) {
        for (var i = 0; i < this.moveHandles.length; i++) {
          this.moveHandles[i].style.left =
            this.moveHandles[i].rowState.x + this.moveHandles[i].rowState.width - 5 + 'px';
          this.moveHandles[i].style.top =
            this.moveHandles[i].rowState.y + this.moveHandles[i].rowState.height / 2 - 6 + 'px';
        }
      }

      if (this.cornerHandles != null) {
        var inset = this.getSelectionBorderInset();
        var ch = this.cornerHandles;
        var w = ch[0].bounds.width / 2;
        var h = ch[0].bounds.height / 2;

        ch[0].bounds.x = this.state.x - w + inset;
        ch[0].bounds.y = this.state.y - h + inset;
        ch[0].redraw();
        ch[1].bounds.x = ch[0].bounds.x + this.state.width - 2 * inset;
        ch[1].bounds.y = ch[0].bounds.y;
        ch[1].redraw();
        ch[2].bounds.x = ch[0].bounds.x;
        ch[2].bounds.y = this.state.y + this.state.height - 2 * inset;
        ch[2].redraw();
        ch[3].bounds.x = ch[1].bounds.x;
        ch[3].bounds.y = ch[2].bounds.y;
        ch[3].redraw();

        for (var i = 0; i < this.cornerHandles.length; i++) {
          this.cornerHandles[i].node.style.display = this.graph.getSelectionCount() == 1 ? '' : 'none';
        }
      }

      // Shows rotation handle only if one vertex is selected
      if (this.rotationShape != null && this.rotationShape.node != null) {
        this.rotationShape.node.style.display =
          this.moveHandles == null &&
          this.graph.getSelectionCount() == 1 &&
          (this.index == null || this.index == mxEvent.ROTATION_HANDLE)
            ? ''
            : 'none';
      }

      vertexHandlerRedrawHandles.apply(this);

      if (this.state != null && this.linkHint != null) {
        var c = new mxPoint(this.state.getCenterX(), this.state.getCenterY());
        var tmp = new mxRectangle(this.state.x, this.state.y - 22, this.state.width + 24, this.state.height + 22);
        var bb = mxUtils.getBoundingBox(tmp, this.state.style[mxConstants.STYLE_ROTATION] || '0', c);
        var rs =
          bb != null
            ? mxUtils.getBoundingBox(this.state, this.state.style[mxConstants.STYLE_ROTATION] || '0')
            : this.state;
        var tb = this.state.text != null ? this.state.text.boundingBox : null;

        if (bb == null) {
          bb = this.state;
        }

        var b = bb.y + bb.height;

        if (tb != null) {
          b = Math.max(b, tb.y + tb.height);
        }

        this.linkHint.style.left = Math.max(0, Math.round(rs.x + (rs.width - this.linkHint.clientWidth) / 2)) + 'px';
        this.linkHint.style.top = Math.round(b + this.verticalOffset / 2 + Editor.hintOffset) + 'px';
      }
    };

    // Destroys special handles
    var vertexHandlerDestroy = mxVertexHandler.prototype.destroy;
    mxVertexHandler.prototype.destroy = function () {
      vertexHandlerDestroy.apply(this, arguments);

      if (this.moveHandles != null) {
        for (var i = 0; i < this.moveHandles.length; i++) {
          if (this.moveHandles[i] != null && this.moveHandles[i].parentNode != null) {
            this.moveHandles[i].parentNode.removeChild(this.moveHandles[i]);
          }
        }

        this.moveHandles = null;
      }

      if (this.cornerHandles != null) {
        for (var i = 0; i < this.cornerHandles.length; i++) {
          if (
            this.cornerHandles[i] != null &&
            this.cornerHandles[i].node != null &&
            this.cornerHandles[i].node.parentNode != null
          ) {
            this.cornerHandles[i].node.parentNode.removeChild(this.cornerHandles[i].node);
          }
        }

        this.cornerHandles = null;
      }

      if (this.linkHint != null) {
        if (this.linkHint.parentNode != null) {
          this.linkHint.parentNode.removeChild(this.linkHint);
        }

        this.linkHint = null;
      }

      if (this.changeHandler != null) {
        this.graph.getSelectionModel().removeListener(this.changeHandler);
        this.graph.getModel().removeListener(this.changeHandler);
        this.changeHandler = null;
      }

      if (this.editingHandler != null) {
        this.graph.removeListener(this.editingHandler);
        this.editingHandler = null;
      }
    };

    var edgeHandlerRedrawHandles = mxEdgeHandler.prototype.redrawHandles;
    mxEdgeHandler.prototype.redrawHandles = function () {
      // Workaround for special case where handler
      // is reset before this which leads to a NPE
      if (this.marker != null) {
        edgeHandlerRedrawHandles.apply(this);

        if (this.state != null && this.linkHint != null) {
          var b = this.state;

          if (this.state.text != null && this.state.text.bounds != null) {
            b = new mxRectangle(b.x, b.y, b.width, b.height);
            b.add(this.state.text.bounds);
          }

          this.linkHint.style.left = Math.max(0, Math.round(b.x + (b.width - this.linkHint.clientWidth) / 2)) + 'px';
          this.linkHint.style.top = Math.round(b.y + b.height + Editor.hintOffset) + 'px';
        }
      }
    };

    var edgeHandlerReset = mxEdgeHandler.prototype.reset;
    mxEdgeHandler.prototype.reset = function () {
      edgeHandlerReset.apply(this, arguments);

      if (this.linkHint != null) {
        this.linkHint.style.visibility = '';
      }
    };

    var edgeHandlerDestroy = mxEdgeHandler.prototype.destroy;
    mxEdgeHandler.prototype.destroy = function () {
      edgeHandlerDestroy.apply(this, arguments);

      if (this.linkHint != null) {
        this.linkHint.parentNode.removeChild(this.linkHint);
        this.linkHint = null;
      }

      if (this.changeHandler != null) {
        this.graph.getModel().removeListener(this.changeHandler);
        this.graph.getSelectionModel().removeListener(this.changeHandler);
        this.changeHandler = null;
      }
    };
  })();
}
/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
Format = function (editorUi, container) {
  this.editorUi = editorUi;
  this.container = container;
};

/**
 * Returns information about the current selection.
 */
Format.prototype.labelIndex = 0;

/**
 * Returns information about the current selection.
 */
Format.prototype.diagramIndex = 0;

/**
 * Returns information about the current selection.
 */
Format.prototype.currentIndex = 0;

/**
 * Returns information about the current selection.
 */
Format.prototype.showCloseButton = true;

/**
 * Background color for inactive tabs.
 */
Format.prototype.inactiveTabBackgroundColor = '#f1f3f4';

/**
 * Background color for inactive tabs.
 */
Format.prototype.roundableShapes = [
  'label',
  'rectangle',
  'internalStorage',
  'corner',
  'parallelogram',
  'swimlane',
  'triangle',
  'trapezoid',
  'ext',
  'step',
  'tee',
  'process',
  'link',
  'rhombus',
  'offPageConnector',
  'loopLimit',
  'hexagon',
  'manualInput',
  'card',
  'curlyBracket',
  'singleArrow',
  'callout',
  'doubleArrow',
  'flexArrow',
  'umlLifeline',
];

/**
 * Adds the label menu items to the given menu and parent.
 */
Format.prototype.init = function () {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;

  this.update = mxUtils.bind(this, function (sender, evt) {
    this.clearSelectionState();
    this.refresh();
  });

  graph.getSelectionModel().addListener(mxEvent.CHANGE, this.update);
  graph.addListener(mxEvent.EDITING_STARTED, this.update);
  graph.addListener(mxEvent.EDITING_STOPPED, this.update);
  graph.getModel().addListener(mxEvent.CHANGE, this.update);
  graph.addListener(
    mxEvent.ROOT,
    mxUtils.bind(this, function () {
      this.refresh();
    })
  );

  ui.addListener(
    'styleChanged',
    mxUtils.bind(this, function (sender, evt) {
      this.refresh();
    })
  );

  editor.addListener(
    'autosaveChanged',
    mxUtils.bind(this, function () {
      this.refresh();
    })
  );

  this.refresh();
};

/**
 * Returns information about the current selection.
 */
Format.prototype.clearSelectionState = function () {
  this.selectionState = null;
};

/**
 * Returns information about the current selection.
 */
Format.prototype.getSelectionState = function () {
  if (this.selectionState == null) {
    this.selectionState = this.createSelectionState();
  }

  return this.selectionState;
};

/**
 * Returns information about the current selection.
 */
Format.prototype.createSelectionState = function () {
  var cells = this.editorUi.editor.graph.getSelectionCells();
  var result = this.initSelectionState();

  for (var i = 0; i < cells.length; i++) {
    this.updateSelectionStateForCell(result, cells[i], cells);
  }

  return result;
};

/**
 * Returns information about the current selection.
 */
Format.prototype.initSelectionState = function () {
  return {
    vertices: [],
    edges: [],
    x: null,
    y: null,
    width: null,
    height: null,
    style: {},
    containsImage: false,
    containsLabel: false,
    fill: true,
    glass: true,
    rounded: true,
    autoSize: false,
    image: true,
    shadow: true,
    lineJumps: true,
    resizable: true,
    table: false,
    cell: false,
    row: false,
    movable: true,
    rotatable: true,
    stroke: true,
  };
};

/**
 * Returns information about the current selection.
 */
Format.prototype.updateSelectionStateForCell = function (result, cell, cells) {
  var graph = this.editorUi.editor.graph;

  if (graph.getModel().isVertex(cell)) {
    result.resizable = result.resizable && graph.isCellResizable(cell);
    result.rotatable = result.rotatable && graph.isCellRotatable(cell);
    result.movable = result.movable && graph.isCellMovable(cell) && !graph.isTableRow(cell) && !graph.isTableCell(cell);
    result.table = result.table || graph.isTable(cell);
    result.cell = result.cell || graph.isTableCell(cell);
    result.row = result.row || graph.isTableRow(cell);
    result.vertices.push(cell);
    var geo = graph.getCellGeometry(cell);

    if (geo != null) {
      if (geo.width > 0) {
        if (result.width == null) {
          result.width = geo.width;
        } else if (result.width != geo.width) {
          result.width = '';
        }
      } else {
        result.containsLabel = true;
      }

      if (geo.height > 0) {
        if (result.height == null) {
          result.height = geo.height;
        } else if (result.height != geo.height) {
          result.height = '';
        }
      } else {
        result.containsLabel = true;
      }

      if (!geo.relative || geo.offset != null) {
        var x = geo.relative ? geo.offset.x : geo.x;
        var y = geo.relative ? geo.offset.y : geo.y;

        if (result.x == null) {
          result.x = x;
        } else if (result.x != x) {
          result.x = '';
        }

        if (result.y == null) {
          result.y = y;
        } else if (result.y != y) {
          result.y = '';
        }
      }
    }
  } else if (graph.getModel().isEdge(cell)) {
    result.edges.push(cell);
    result.resizable = false;
    result.rotatable = false;
    result.movable = false;
  }

  var state = graph.view.getState(cell);

  if (state != null) {
    result.autoSize = result.autoSize || this.isAutoSizeState(state);
    result.glass = result.glass && this.isGlassState(state);
    result.rounded = result.rounded && this.isRoundedState(state);
    result.lineJumps = result.lineJumps && this.isLineJumpState(state);
    result.image = result.image && this.isImageState(state);
    result.shadow = result.shadow && this.isShadowState(state);
    result.fill = result.fill && this.isFillState(state);
    result.stroke = result.stroke && this.isStrokeState(state);

    var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null);
    result.containsImage = result.containsImage || shape == 'image';

    for (var key in state.style) {
      var value = state.style[key];

      if (value != null) {
        if (result.style[key] == null) {
          result.style[key] = value;
        } else if (result.style[key] != value) {
          result.style[key] = '';
        }
      }
    }
  }
};

/**
 * Returns information about the current selection.
 */
Format.prototype.isFillState = function (state) {
  return (
    !this.isSpecialColor(state.style[mxConstants.STYLE_FILLCOLOR]) &&
    (state.view.graph.model.isVertex(state.cell) ||
      mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'arrow' ||
      mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'filledEdge' ||
      mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) == 'flexArrow')
  );
};

/**
 * Returns information about the current selection.
 */
Format.prototype.isStrokeState = function (state) {
  return !this.isSpecialColor(state.style[mxConstants.STYLE_STROKECOLOR]);
};

/**
 * Returns information about the current selection.
 */
Format.prototype.isSpecialColor = function (color) {
  return (
    mxUtils.indexOf(
      [mxConstants.STYLE_STROKECOLOR, mxConstants.STYLE_FILLCOLOR, 'inherit', 'swimlane', 'indicated'],
      color
    ) >= 0
  );
};

/**
 * Returns information about the current selection.
 */
Format.prototype.isGlassState = function (state) {
  var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null);

  return (
    shape == 'label' ||
    shape == 'rectangle' ||
    shape == 'internalStorage' ||
    shape == 'ext' ||
    shape == 'umlLifeline' ||
    shape == 'swimlane' ||
    shape == 'process'
  );
};

/**
 * Returns information about the current selection.
 */
Format.prototype.isRoundedState = function (state) {
  return state.shape != null
    ? state.shape.isRoundable()
    : mxUtils.indexOf(this.roundableShapes, mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null)) >= 0;
};

/**
 * Returns information about the current selection.
 */
Format.prototype.isLineJumpState = function (state) {
  var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null);
  var curved = mxUtils.getValue(state.style, mxConstants.STYLE_CURVED, false);

  return !curved && (shape == 'connector' || shape == 'filledEdge');
};

/**
 * Returns information about the current selection.
 */
Format.prototype.isAutoSizeState = function (state) {
  return mxUtils.getValue(state.style, mxConstants.STYLE_AUTOSIZE, null) == '1';
};

/**
 * Returns information about the current selection.
 */
Format.prototype.isImageState = function (state) {
  var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null);

  return shape == 'label' || shape == 'image';
};

/**
 * Returns information about the current selection.
 */
Format.prototype.isShadowState = function (state) {
  var shape = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null);

  return shape != 'image';
};

/**
 * Adds the label menu items to the given menu and parent.
 */
Format.prototype.clear = function () {
  this.container.innerHTML = '';

  // Destroy existing panels
  if (this.panels != null) {
    for (var i = 0; i < this.panels.length; i++) {
      this.panels[i].destroy();
    }
  }

  this.panels = [];
};

/**
 * Adds the label menu items to the given menu and parent.
 */
Format.prototype.refresh = function () {
  // Performance tweak: No refresh needed if not visible
  if (this.container.style.width == '0px') {
    return;
  }

  this.clear();
  var ui = this.editorUi;
  var graph = ui.editor.graph;

  var div = document.createElement('div');
  div.style.whiteSpace = 'nowrap';
  div.style.color = 'rgb(112, 112, 112)';
  div.style.textAlign = 'left';
  div.style.cursor = 'default';

  var label = document.createElement('div');
  label.className = 'geFormatSection';
  label.style.textAlign = 'center';
  label.style.fontWeight = 'bold';
  label.style.paddingTop = '8px';
  label.style.fontSize = '13px';
  label.style.borderWidth = '0px 0px 1px 1px';
  label.style.borderStyle = 'solid';
  label.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block';
  label.style.height = mxClient.IS_QUIRKS ? '34px' : '25px';
  label.style.overflow = 'hidden';
  label.style.width = '100%';
  this.container.appendChild(div);

  // Prevents text selection
  mxEvent.addListener(
    label,
    mxClient.IS_POINTER ? 'pointerdown' : 'mousedown',
    mxUtils.bind(this, function (evt) {
      evt.preventDefault();
    })
  );

  var containsLabel = this.getSelectionState().containsLabel;
  var currentLabel = null;
  var currentPanel = null;

  var addClickHandler = mxUtils.bind(this, function (elt, panel, index) {
    var clickHandler = mxUtils.bind(this, function (evt) {
      if (currentLabel != elt) {
        if (containsLabel) {
          this.labelIndex = index;
        } else if (graph.isSelectionEmpty()) {
          this.diagramIndex = index;
        } else {
          this.currentIndex = index;
        }

        if (currentLabel != null) {
          currentLabel.style.backgroundColor = this.inactiveTabBackgroundColor;
          currentLabel.style.borderBottomWidth = '1px';
        }

        currentLabel = elt;
        currentLabel.style.backgroundColor = '';
        currentLabel.style.borderBottomWidth = '0px';

        if (currentPanel != panel) {
          if (currentPanel != null) {
            currentPanel.style.display = 'none';
          }

          currentPanel = panel;
          currentPanel.style.display = '';
        }
      }
    });

    mxEvent.addListener(elt, 'click', clickHandler);

    // Prevents text selection
    mxEvent.addListener(
      elt,
      mxClient.IS_POINTER ? 'pointerdown' : 'mousedown',
      mxUtils.bind(this, function (evt) {
        evt.preventDefault();
      })
    );

    if (index == (containsLabel ? this.labelIndex : graph.isSelectionEmpty() ? this.diagramIndex : this.currentIndex)) {
      // Invokes handler directly as a workaround for no click on DIV in KHTML.
      clickHandler();
    }
  });

  var idx = 0;

  if (graph.isSelectionEmpty()) {
    mxUtils.write(label, mxResources.get('diagram'));
    label.style.borderLeftWidth = '0px';

    div.appendChild(label);
    var diagramPanel = div.cloneNode(false);
    this.panels.push(new DiagramFormatPanel(this, ui, diagramPanel));
    this.container.appendChild(diagramPanel);

    if (Editor.styles != null) {
      diagramPanel.style.display = 'none';
      label.style.width = this.showCloseButton ? '106px' : '50%';
      label.style.cursor = 'pointer';
      label.style.backgroundColor = this.inactiveTabBackgroundColor;

      var label2 = label.cloneNode(false);
      label2.style.borderLeftWidth = '1px';
      label2.style.borderRightWidth = '1px';
      label2.style.backgroundColor = this.inactiveTabBackgroundColor;

      addClickHandler(label, diagramPanel, idx++);

      var stylePanel = div.cloneNode(false);
      stylePanel.style.display = 'none';
      mxUtils.write(label2, mxResources.get('style'));
      div.appendChild(label2);
      this.panels.push(new DiagramStylePanel(this, ui, stylePanel));
      this.container.appendChild(stylePanel);

      addClickHandler(label2, stylePanel, idx++);
    }

    // Adds button to hide the format panel since
    // people don't seem to find the toolbar button
    // and the menu item in the format menu
    if (this.showCloseButton) {
      var label2 = label.cloneNode(false);
      label2.style.borderLeftWidth = '1px';
      label2.style.borderRightWidth = '1px';
      label2.style.borderBottomWidth = '1px';
      label2.style.backgroundColor = this.inactiveTabBackgroundColor;
      label2.style.position = 'absolute';
      label2.style.right = '0px';
      label2.style.top = '0px';
      label2.style.width = '25px';

      var img = document.createElement('img');
      img.setAttribute('border', '0');
      img.setAttribute('src', Dialog.prototype.closeImage);
      img.setAttribute('title', mxResources.get('hide'));
      img.style.position = 'absolute';
      img.style.display = 'block';
      img.style.right = '0px';
      img.style.top = '8px';
      img.style.cursor = 'pointer';
      img.style.marginTop = '1px';
      img.style.marginRight = '6px';
      img.style.border = '1px solid transparent';
      img.style.padding = '1px';
      img.style.opacity = 0.5;
      label2.appendChild(img);

      mxEvent.addListener(img, 'click', function () {
        ui.actions.get('formatPanel').funct();
      });

      div.appendChild(label2);
    }
  } else if (graph.isEditing()) {
    mxUtils.write(label, mxResources.get('text'));
    div.appendChild(label);
    this.panels.push(new TextFormatPanel(this, ui, div));
  } else {
    label.style.backgroundColor = this.inactiveTabBackgroundColor;
    label.style.borderLeftWidth = '1px';
    label.style.cursor = 'pointer';
    label.style.width = containsLabel ? '50%' : '33.3%';
    var label2 = label.cloneNode(false);
    var label3 = label2.cloneNode(false);

    // Workaround for ignored background in IE
    label2.style.backgroundColor = this.inactiveTabBackgroundColor;
    label3.style.backgroundColor = this.inactiveTabBackgroundColor;

    // Style
    if (containsLabel) {
      label2.style.borderLeftWidth = '0px';
    } else {
      label.style.borderLeftWidth = '0px';
      mxUtils.write(label, mxResources.get('style'));
      div.appendChild(label);

      var stylePanel = div.cloneNode(false);
      stylePanel.style.display = 'none';
      this.panels.push(new StyleFormatPanel(this, ui, stylePanel));
      this.container.appendChild(stylePanel);

      addClickHandler(label, stylePanel, idx++);
    }

    // Text
    mxUtils.write(label2, mxResources.get('text'));
    div.appendChild(label2);

    var textPanel = div.cloneNode(false);
    textPanel.style.display = 'none';
    this.panels.push(new TextFormatPanel(this, ui, textPanel));
    this.container.appendChild(textPanel);

    // Arrange
    mxUtils.write(label3, mxResources.get('arrange'));
    div.appendChild(label3);

    var arrangePanel = div.cloneNode(false);
    arrangePanel.style.display = 'none';
    this.panels.push(new ArrangePanel(this, ui, arrangePanel));
    this.container.appendChild(arrangePanel);

    addClickHandler(label2, textPanel, idx++);
    addClickHandler(label3, arrangePanel, idx++);
  }
};

/**
 * Base class for format panels.
 */
BaseFormatPanel = function (format, editorUi, container) {
  this.format = format;
  this.editorUi = editorUi;
  this.container = container;
  this.listeners = [];
};

/**
 *
 */
BaseFormatPanel.prototype.buttonBackgroundColor = 'white';

/**
 * Adds the given color option.
 */
BaseFormatPanel.prototype.getSelectionState = function () {
  var graph = this.editorUi.editor.graph;
  var cells = graph.getSelectionCells();
  var shape = null;

  for (var i = 0; i < cells.length; i++) {
    var state = graph.view.getState(cells[i]);

    if (state != null) {
      var tmp = mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null);

      if (tmp != null) {
        if (shape == null) {
          shape = tmp;
        } else if (shape != tmp) {
          return null;
        }
      }
    }
  }

  return shape;
};

/**
 * Install input handler.
 */
BaseFormatPanel.prototype.installInputHandler = function (
  input,
  key,
  defaultValue,
  min,
  max,
  unit,
  textEditFallback,
  isFloat
) {
  unit = unit != null ? unit : '';
  isFloat = isFloat != null ? isFloat : false;

  var ui = this.editorUi;
  var graph = ui.editor.graph;

  min = min != null ? min : 1;
  max = max != null ? max : 999;

  var selState = null;
  var updating = false;

  var update = mxUtils.bind(this, function (evt) {
    var value = isFloat ? parseFloat(input.value) : parseInt(input.value);

    // Special case: angle mod 360
    if (!isNaN(value) && key == mxConstants.STYLE_ROTATION) {
      // Workaround for decimal rounding errors in floats is to
      // use integer and round all numbers to two decimal point
      value = mxUtils.mod(Math.round(value * 100), 36000) / 100;
    }

    value = Math.min(max, Math.max(min, isNaN(value) ? defaultValue : value));

    if (graph.cellEditor.isContentEditing() && textEditFallback) {
      if (!updating) {
        updating = true;

        if (selState != null) {
          graph.cellEditor.restoreSelection(selState);
          selState = null;
        }

        textEditFallback(value);
        input.value = value + unit;

        // Restore focus and selection in input
        updating = false;
      }
    } else if (value != mxUtils.getValue(this.format.getSelectionState().style, key, defaultValue)) {
      if (graph.isEditing()) {
        graph.stopEditing(true);
      }

      graph.getModel().beginUpdate();
      try {
        var cells = graph.getSelectionCells();
        graph.setCellStyles(key, value, cells);

        // Handles special case for fontSize where HTML labels are parsed and updated
        if (key == mxConstants.STYLE_FONTSIZE) {
          graph.updateLabelElements(graph.getSelectionCells(), function (elt) {
            elt.style.fontSize = value + 'px';
            elt.removeAttribute('size');
          });
        }

        for (var i = 0; i < cells.length; i++) {
          if (graph.model.getChildCount(cells[i]) == 0) {
            graph.autoSizeCell(cells[i], false);
          }
        }

        ui.fireEvent(new mxEventObject('styleChanged', 'keys', [key], 'values', [value], 'cells', cells));
      } finally {
        graph.getModel().endUpdate();
      }
    }

    input.value = value + unit;
    mxEvent.consume(evt);
  });

  if (textEditFallback && graph.cellEditor.isContentEditing()) {
    // KNOWN: Arrow up/down clear selection text in quirks/IE 8
    // Text size via arrow button limits to 16 in IE11. Why?
    mxEvent.addListener(input, 'mousedown', function () {
      if (document.activeElement == graph.cellEditor.textarea) {
        selState = graph.cellEditor.saveSelection();
      }
    });

    mxEvent.addListener(input, 'touchstart', function () {
      if (document.activeElement == graph.cellEditor.textarea) {
        selState = graph.cellEditor.saveSelection();
      }
    });
  }

  mxEvent.addListener(input, 'change', update);
  mxEvent.addListener(input, 'blur', update);

  return update;
};

/**
 * Adds the given option.
 */
BaseFormatPanel.prototype.createPanel = function () {
  var div = document.createElement('div');
  div.className = 'geFormatSection';
  div.style.padding = '12px 0px 12px 18px';

  return div;
};

/**
 * Adds the given option.
 */
BaseFormatPanel.prototype.createTitle = function (title) {
  var div = document.createElement('div');
  div.style.padding = '0px 0px 6px 0px';
  div.style.whiteSpace = 'nowrap';
  div.style.overflow = 'hidden';
  div.style.width = '200px';
  div.style.fontWeight = 'bold';
  mxUtils.write(div, title);

  return div;
};

/**
 *
 */
BaseFormatPanel.prototype.createStepper = function (input, update, step, height, disableFocus, defaultValue, isFloat) {
  step = step != null ? step : 1;
  height = height != null ? height : 8;

  if (mxClient.IS_QUIRKS) {
    height = height - 2;
  } else if (mxClient.IS_MT || document.documentMode >= 8) {
    height = height + 1;
  }

  var stepper = document.createElement('div');
  mxUtils.setPrefixedStyle(stepper.style, 'borderRadius', '3px');
  stepper.style.border = '1px solid rgb(192, 192, 192)';
  stepper.style.position = 'absolute';

  var up = document.createElement('div');
  up.style.borderBottom = '1px solid rgb(192, 192, 192)';
  up.style.position = 'relative';
  up.style.height = height + 'px';
  up.style.width = '10px';
  up.className = 'geBtnUp';
  stepper.appendChild(up);

  var down = up.cloneNode(false);
  down.style.border = 'none';
  down.style.height = height + 'px';
  down.className = 'geBtnDown';
  stepper.appendChild(down);

  mxEvent.addListener(down, 'click', function (evt) {
    if (input.value == '') {
      input.value = defaultValue || '2';
    }

    var val = isFloat ? parseFloat(input.value) : parseInt(input.value);

    if (!isNaN(val)) {
      input.value = val - step;

      if (update != null) {
        update(evt);
      }
    }

    mxEvent.consume(evt);
  });

  mxEvent.addListener(up, 'click', function (evt) {
    if (input.value == '') {
      input.value = defaultValue || '0';
    }

    var val = isFloat ? parseFloat(input.value) : parseInt(input.value);

    if (!isNaN(val)) {
      input.value = val + step;

      if (update != null) {
        update(evt);
      }
    }

    mxEvent.consume(evt);
  });

  // Disables transfer of focus to DIV but also :active CSS
  // so it's only used for fontSize where the focus should
  // stay on the selected text, but not for any other input.
  if (disableFocus) {
    var currentSelection = null;

    mxEvent.addGestureListeners(
      stepper,
      function (evt) {
        // Workaround for lost current selection in page because of focus in IE
        if (mxClient.IS_QUIRKS || document.documentMode == 8) {
          currentSelection = document.selection.createRange();
        }

        mxEvent.consume(evt);
      },
      null,
      function (evt) {
        // Workaround for lost current selection in page because of focus in IE
        if (currentSelection != null) {
          try {
            currentSelection.select();
          } catch (e) {
            // ignore
          }

          currentSelection = null;
          mxEvent.consume(evt);
        }
      }
    );
  }

  return stepper;
};

/**
 * Adds the given option.
 */
BaseFormatPanel.prototype.createOption = function (label, isCheckedFn, setCheckedFn, listener, fn) {
  var div = document.createElement('div');
  div.style.padding = '6px 0px 1px 0px';
  div.style.whiteSpace = 'nowrap';
  div.style.overflow = 'hidden';
  div.style.width = '200px';
  div.style.height = mxClient.IS_QUIRKS ? '27px' : '18px';

  var cb = document.createElement('input');
  cb.setAttribute('type', 'checkbox');
  cb.style.margin = '0px 6px 0px 0px';
  div.appendChild(cb);

  var span = document.createElement('span');
  mxUtils.write(span, label);
  div.appendChild(span);

  var applying = false;
  var value = isCheckedFn();

  var apply = function (newValue) {
    if (!applying) {
      applying = true;

      if (newValue) {
        cb.setAttribute('checked', 'checked');
        cb.defaultChecked = true;
        cb.checked = true;
      } else {
        cb.removeAttribute('checked');
        cb.defaultChecked = false;
        cb.checked = false;
      }

      if (value != newValue) {
        value = newValue;

        // Checks if the color value needs to be updated in the model
        if (isCheckedFn() != value) {
          setCheckedFn(value);
        }
      }

      applying = false;
    }
  };

  mxEvent.addListener(div, 'click', function (evt) {
    if (cb.getAttribute('disabled') != 'disabled') {
      // Toggles checkbox state for click on label
      var source = mxEvent.getSource(evt);

      if (source == div || source == span) {
        cb.checked = !cb.checked;
      }

      apply(cb.checked);
    }
  });

  apply(value);

  if (listener != null) {
    listener.install(apply);
    this.listeners.push(listener);
  }

  if (fn != null) {
    fn(div);
  }

  return div;
};

/**
 * The string 'null' means use null in values.
 */
BaseFormatPanel.prototype.createCellOption = function (
  label,
  key,
  defaultValue,
  enabledValue,
  disabledValue,
  fn,
  action,
  stopEditing
) {
  enabledValue = enabledValue != null ? (enabledValue == 'null' ? null : enabledValue) : '1';
  disabledValue = disabledValue != null ? (disabledValue == 'null' ? null : disabledValue) : '0';

  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;

  return this.createOption(
    label,
    function () {
      // Seems to be null sometimes, not sure why...
      var state = graph.view.getState(graph.getSelectionCell());

      if (state != null) {
        return mxUtils.getValue(state.style, key, defaultValue) != disabledValue;
      }

      return null;
    },
    function (checked) {
      if (stopEditing) {
        graph.stopEditing();
      }

      if (action != null) {
        action.funct();
      } else {
        graph.getModel().beginUpdate();
        try {
          var value = checked ? enabledValue : disabledValue;
          graph.setCellStyles(key, value, graph.getSelectionCells());

          if (fn != null) {
            fn(graph.getSelectionCells(), value);
          }

          ui.fireEvent(
            new mxEventObject('styleChanged', 'keys', [key], 'values', [value], 'cells', graph.getSelectionCells())
          );
        } finally {
          graph.getModel().endUpdate();
        }
      }
    },
    {
      install: function (apply) {
        this.listener = function () {
          // Seems to be null sometimes, not sure why...
          var state = graph.view.getState(graph.getSelectionCell());

          if (state != null) {
            apply(mxUtils.getValue(state.style, key, defaultValue) != disabledValue);
          }
        };

        graph.getModel().addListener(mxEvent.CHANGE, this.listener);
      },
      destroy: function () {
        graph.getModel().removeListener(this.listener);
      },
    }
  );
};

/**
 * Adds the given color option.
 */
BaseFormatPanel.prototype.createColorOption = function (
  label,
  getColorFn,
  setColorFn,
  defaultColor,
  listener,
  callbackFn,
  hideCheckbox
) {
  var div = document.createElement('div');
  div.style.padding = '6px 0px 1px 0px';
  div.style.whiteSpace = 'nowrap';
  div.style.overflow = 'hidden';
  div.style.width = '200px';
  div.style.height = mxClient.IS_QUIRKS ? '27px' : '18px';

  var cb = document.createElement('input');
  cb.setAttribute('type', 'checkbox');
  cb.style.margin = '0px 6px 0px 0px';

  if (!hideCheckbox) {
    div.appendChild(cb);
  }

  var span = document.createElement('span');
  mxUtils.write(span, label);
  div.appendChild(span);

  var value = getColorFn();
  var applying = false;
  var btn = null;

  var apply = function (color, disableUpdate, forceUpdate) {
    if (!applying) {
      applying = true;
      color = /(^#?[a-zA-Z0-9]*$)/.test(color) ? color : defaultColor;
      btn.innerHTML =
        '<div style="width:' +
        (mxClient.IS_QUIRKS ? '30' : '36') +
        'px;height:12px;margin:3px;border:1px solid black;background-color:' +
        mxUtils.htmlEntities(color != null && color != mxConstants.NONE ? color : defaultColor) +
        ';"></div>';

      // Fine-tuning in Firefox, quirks mode and IE8 standards
      if (mxClient.IS_QUIRKS || document.documentMode == 8) {
        btn.firstChild.style.margin = '0px';
      }

      if (color != null && color != mxConstants.NONE) {
        cb.setAttribute('checked', 'checked');
        cb.defaultChecked = true;
        cb.checked = true;
      } else {
        cb.removeAttribute('checked');
        cb.defaultChecked = false;
        cb.checked = false;
      }

      btn.style.display = cb.checked || hideCheckbox ? '' : 'none';

      if (callbackFn != null) {
        callbackFn(color);
      }

      if (!disableUpdate) {
        value = color;

        // Checks if the color value needs to be updated in the model
        if (forceUpdate || hideCheckbox || getColorFn() != value) {
          setColorFn(value);
        }
      }

      applying = false;
    }
  };

  btn = mxUtils.button(
    '',
    mxUtils.bind(this, function (evt) {
      this.editorUi.pickColor(value, function (color) {
        apply(color, null, true);
      });
      mxEvent.consume(evt);
    })
  );

  btn.style.position = 'absolute';
  btn.style.marginTop = '-4px';
  btn.style.right = mxClient.IS_QUIRKS ? '0px' : '20px';
  btn.style.height = '22px';
  btn.className = 'geColorBtn';
  btn.style.display = cb.checked || hideCheckbox ? '' : 'none';
  div.appendChild(btn);

  mxEvent.addListener(div, 'click', function (evt) {
    var source = mxEvent.getSource(evt);

    if (source == cb || source.nodeName != 'INPUT') {
      // Toggles checkbox state for click on label
      if (source != cb) {
        cb.checked = !cb.checked;
      }

      // Overrides default value with current value to make it easier
      // to restore previous value if the checkbox is clicked twice
      if (!cb.checked && value != null && value != mxConstants.NONE && defaultColor != mxConstants.NONE) {
        defaultColor = value;
      }

      apply(cb.checked ? defaultColor : mxConstants.NONE);
    }
  });

  apply(value, true);

  if (listener != null) {
    listener.install(apply);
    this.listeners.push(listener);
  }

  return div;
};

/**
 *
 */
BaseFormatPanel.prototype.createCellColorOption = function (label, colorKey, defaultColor, callbackFn, setStyleFn) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;

  return this.createColorOption(
    label,
    function () {
      // Seems to be null sometimes, not sure why...
      var state = graph.view.getState(graph.getSelectionCell());

      if (state != null) {
        return mxUtils.getValue(state.style, colorKey, null);
      }

      return null;
    },
    function (color) {
      graph.getModel().beginUpdate();
      try {
        graph.setCellStyles(colorKey, color, graph.getSelectionCells());

        if (setStyleFn != null) {
          setStyleFn(color);
        }

        ui.fireEvent(
          new mxEventObject('styleChanged', 'keys', [colorKey], 'values', [color], 'cells', graph.getSelectionCells())
        );
      } finally {
        graph.getModel().endUpdate();
      }
    },
    defaultColor || mxConstants.NONE,
    {
      install: function (apply) {
        this.listener = function () {
          // Seems to be null sometimes, not sure why...
          var state = graph.view.getState(graph.getSelectionCell());

          if (state != null) {
            apply(mxUtils.getValue(state.style, colorKey, null));
          }
        };

        graph.getModel().addListener(mxEvent.CHANGE, this.listener);
      },
      destroy: function () {
        graph.getModel().removeListener(this.listener);
      },
    },
    callbackFn
  );
};

/**
 *
 */
BaseFormatPanel.prototype.addArrow = function (elt, height) {
  height = height != null ? height : 10;

  var arrow = document.createElement('div');
  arrow.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block';
  arrow.style.padding = '6px';
  arrow.style.paddingRight = '4px';

  var m = 10 - height;

  if (m == 2) {
    arrow.style.paddingTop = 6 + 'px';
  } else if (m > 0) {
    arrow.style.paddingTop = 6 - m + 'px';
  } else {
    arrow.style.marginTop = '-2px';
  }

  arrow.style.height = height + 'px';
  arrow.style.borderLeft = '1px solid #a0a0a0';
  arrow.innerHTML =
    '<img border="0" src="' +
    (mxClient.IS_SVG
      ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAHBJREFUeNpidHB2ZyAGsACxDRBPIKCuA6TwCBB/h2rABu4A8SYmKCcXiP/iUFgAxL9gCi8A8SwsirZCMQMTkmANEH9E4v+CmsaArvAdyNFI/FlQ92EoBIE+qCRIUz168DBgsU4OqhinQpgHMABAgAEALY4XLIsJ20oAAAAASUVORK5CYII='
      : IMAGE_PATH + '/dropdown.png') +
    '" style="margin-bottom:4px;">';
  mxUtils.setOpacity(arrow, 70);

  var symbol = elt.getElementsByTagName('div')[0];

  if (symbol != null) {
    symbol.style.paddingRight = '6px';
    symbol.style.marginLeft = '4px';
    symbol.style.marginTop = '-1px';
    symbol.style.display = mxClient.IS_QUIRKS ? 'inline' : 'inline-block';
    mxUtils.setOpacity(symbol, 60);
  }

  mxUtils.setOpacity(elt, 100);
  elt.style.border = '1px solid #a0a0a0';
  elt.style.backgroundColor = this.buttonBackgroundColor;
  elt.style.backgroundImage = 'none';
  elt.style.width = 'auto';
  elt.className += ' geColorBtn';
  mxUtils.setPrefixedStyle(elt.style, 'borderRadius', '3px');

  elt.appendChild(arrow);

  return symbol;
};

/**
 *
 */
BaseFormatPanel.prototype.addUnitInput = function (
  container,
  unit,
  right,
  width,
  update,
  step,
  marginTop,
  disableFocus,
  isFloat
) {
  marginTop = marginTop != null ? marginTop : 0;

  var input = document.createElement('input');
  input.style.position = 'absolute';
  input.style.textAlign = 'right';
  input.style.marginTop = '-2px';
  input.style.right = right + 12 + 'px';
  input.style.width = width + 'px';
  container.appendChild(input);

  var stepper = this.createStepper(input, update, step, null, disableFocus, null, isFloat);
  stepper.style.marginTop = marginTop - 2 + 'px';
  stepper.style.right = right + 'px';
  container.appendChild(stepper);

  return input;
};

/**
 *
 */
BaseFormatPanel.prototype.createRelativeOption = function (label, key, width, handler, init) {
  width = width != null ? width : 44;

  var graph = this.editorUi.editor.graph;
  var div = this.createPanel();
  div.style.paddingTop = '10px';
  div.style.paddingBottom = '10px';
  mxUtils.write(div, label);
  div.style.fontWeight = 'bold';

  var update = mxUtils.bind(this, function (evt) {
    if (handler != null) {
      handler(input);
    } else {
      var value = parseInt(input.value);
      value = Math.min(100, Math.max(0, isNaN(value) ? 100 : value));
      var state = graph.view.getState(graph.getSelectionCell());

      if (state != null && value != mxUtils.getValue(state.style, key, 100)) {
        // Removes entry in style (assumes 100 is default for relative values)
        if (value == 100) {
          value = null;
        }

        graph.setCellStyles(key, value, graph.getSelectionCells());
        this.editorUi.fireEvent(
          new mxEventObject('styleChanged', 'keys', [key], 'values', [value], 'cells', graph.getSelectionCells())
        );
      }

      input.value = (value != null ? value : '100') + ' %';
    }

    mxEvent.consume(evt);
  });

  var input = this.addUnitInput(div, '%', 20, width, update, 10, -15, handler != null);

  if (key != null) {
    var listener = mxUtils.bind(this, function (sender, evt, force) {
      if (force || input != document.activeElement) {
        var ss = this.format.getSelectionState();
        var tmp = parseInt(mxUtils.getValue(ss.style, key, 100));
        input.value = isNaN(tmp) ? '' : tmp + ' %';
      }
    });

    mxEvent.addListener(input, 'keydown', function (e) {
      if (e.keyCode == 13) {
        graph.container.focus();
        mxEvent.consume(e);
      } else if (e.keyCode == 27) {
        listener(null, null, true);
        graph.container.focus();
        mxEvent.consume(e);
      }
    });

    graph.getModel().addListener(mxEvent.CHANGE, listener);
    this.listeners.push({
      destroy: function () {
        graph.getModel().removeListener(listener);
      },
    });
    listener();
  }

  mxEvent.addListener(input, 'blur', update);
  mxEvent.addListener(input, 'change', update);

  if (init != null) {
    init(input);
  }

  return div;
};

/**
 *
 */
BaseFormatPanel.prototype.addLabel = function (div, title, right, width) {
  width = width != null ? width : 61;

  var label = document.createElement('div');
  mxUtils.write(label, title);
  label.style.position = 'absolute';
  label.style.right = right + 'px';
  label.style.width = width + 'px';
  label.style.marginTop = '6px';
  label.style.textAlign = 'center';
  div.appendChild(label);
};

/**
 *
 */
BaseFormatPanel.prototype.addKeyHandler = function (input, listener) {
  mxEvent.addListener(
    input,
    'keydown',
    mxUtils.bind(this, function (e) {
      if (e.keyCode == 13) {
        this.editorUi.editor.graph.container.focus();
        mxEvent.consume(e);
      } else if (e.keyCode == 27) {
        if (listener != null) {
          listener(null, null, true);
        }

        this.editorUi.editor.graph.container.focus();
        mxEvent.consume(e);
      }
    })
  );
};

/**
 *
 */
BaseFormatPanel.prototype.styleButtons = function (elts) {
  for (var i = 0; i < elts.length; i++) {
    mxUtils.setPrefixedStyle(elts[i].style, 'borderRadius', '3px');
    mxUtils.setOpacity(elts[i], 100);
    elts[i].style.border = '1px solid #a0a0a0';
    elts[i].style.padding = '4px';
    elts[i].style.paddingTop = '3px';
    elts[i].style.paddingRight = '1px';
    elts[i].style.margin = '1px';
    elts[i].style.width = '24px';
    elts[i].style.height = '20px';
    elts[i].className += ' geColorBtn';
  }
};

/**
 * Adds the label menu items to the given menu and parent.
 */
BaseFormatPanel.prototype.destroy = function () {
  if (this.listeners != null) {
    for (var i = 0; i < this.listeners.length; i++) {
      this.listeners[i].destroy();
    }

    this.listeners = null;
  }
};

/**
 * Adds the label menu items to the given menu and parent.
 */
ArrangePanel = function (format, editorUi, container) {
  BaseFormatPanel.call(this, format, editorUi, container);
  this.init();
};

mxUtils.extend(ArrangePanel, BaseFormatPanel);

/**
 * Adds the label menu items to the given menu and parent.
 */
ArrangePanel.prototype.init = function () {
  var graph = this.editorUi.editor.graph;
  var ss = this.format.getSelectionState();

  this.container.appendChild(this.addLayerOps(this.createPanel()));
  // Special case that adds two panels
  this.addGeometry(this.container);
  this.addEdgeGeometry(this.container);

  if (!ss.containsLabel || ss.edges.length == 0) {
    this.container.appendChild(this.addAngle(this.createPanel()));
  }

  if (!ss.containsLabel && ss.edges.length == 0 && ss.style.shape != 'rectangle' && ss.style.shape != 'label') {
    this.container.appendChild(this.addFlip(this.createPanel()));
  }

  if (ss.vertices.length > 1) {
    this.container.appendChild(this.addAlign(this.createPanel()));
    this.container.appendChild(this.addDistribute(this.createPanel()));
  }

  if (graph.isTable(ss.vertices[0]) || graph.isTableRow(ss.vertices[0]) || graph.isTableCell(ss.vertices[0])) {
    this.container.appendChild(this.addTable(this.createPanel()));
  }

  this.container.appendChild(this.addGroupOps(this.createPanel()));

  if (ss.containsLabel) {
    // Adds functions from hidden style format panel
    var span = document.createElement('div');
    span.style.width = '100%';
    span.style.marginTop = '0px';
    span.style.fontWeight = 'bold';
    span.style.padding = '10px 0 0 18px';
    mxUtils.write(span, mxResources.get('style'));
    this.container.appendChild(span);

    new StyleFormatPanel(this.format, this.editorUi, this.container);
  }
};

/**
 *
 */
ArrangePanel.prototype.addTable = function (div) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;
  var ss = this.format.getSelectionState();
  div.style.paddingTop = '6px';
  div.style.paddingBottom = '10px';

  var span = document.createElement('div');
  span.style.marginTop = '2px';
  span.style.marginBottom = '8px';
  span.style.fontWeight = 'bold';
  mxUtils.write(span, mxResources.get('table'));
  div.appendChild(span);

  var panel = document.createElement('div');
  panel.style.position = 'relative';
  panel.style.paddingLeft = '0px';
  panel.style.borderWidth = '0px';
  panel.className = 'geToolbarContainer';

  var btns = [
    ui.toolbar.addButton(
      'geSprite-insertcolumnbefore',
      mxResources.get('insertColumnBefore'),
      mxUtils.bind(this, function () {
        try {
          graph.insertTableColumn(ss.vertices[0], true);
        } catch (e) {
          ui.handleError(e);
        }
      }),
      panel
    ),
    ui.toolbar.addButton(
      'geSprite-insertcolumnafter',
      mxResources.get('insertColumnAfter'),
      mxUtils.bind(this, function () {
        try {
          graph.insertTableColumn(ss.vertices[0], false);
        } catch (e) {
          ui.handleError(e);
        }
      }),
      panel
    ),
    ui.toolbar.addButton(
      'geSprite-deletecolumn',
      mxResources.get('deleteColumn'),
      mxUtils.bind(this, function () {
        try {
          graph.deleteTableColumn(ss.vertices[0]);
        } catch (e) {
          ui.handleError(e);
        }
      }),
      panel
    ),
    ui.toolbar.addButton(
      'geSprite-insertrowbefore',
      mxResources.get('insertRowBefore'),
      mxUtils.bind(this, function () {
        try {
          graph.insertTableRow(ss.vertices[0], true);
        } catch (e) {
          ui.handleError(e);
        }
      }),
      panel
    ),
    ui.toolbar.addButton(
      'geSprite-insertrowafter',
      mxResources.get('insertRowAfter'),
      mxUtils.bind(this, function () {
        try {
          graph.insertTableRow(ss.vertices[0], false);
        } catch (e) {
          ui.handleError(e);
        }
      }),
      panel
    ),
    ui.toolbar.addButton(
      'geSprite-deleterow',
      mxResources.get('deleteRow'),
      mxUtils.bind(this, function () {
        try {
          graph.deleteTableRow(ss.vertices[0]);
        } catch (e) {
          ui.handleError(e);
        }
      }),
      panel
    ),
  ];
  this.styleButtons(btns);
  div.appendChild(panel);
  btns[2].style.marginRight = '9px';

  return div;
};

/**
 *
 */
ArrangePanel.prototype.addLayerOps = function (div) {
  var ui = this.editorUi;

  var btn = mxUtils.button(mxResources.get('toFront'), function (evt) {
    ui.actions.get('toFront').funct();
  });

  btn.setAttribute('title', mxResources.get('toFront') + ' (' + this.editorUi.actions.get('toFront').shortcut + ')');
  btn.style.width = '100px';
  btn.style.marginRight = '2px';
  div.appendChild(btn);

  var btn = mxUtils.button(mxResources.get('toBack'), function (evt) {
    ui.actions.get('toBack').funct();
  });

  btn.setAttribute('title', mxResources.get('toBack') + ' (' + this.editorUi.actions.get('toBack').shortcut + ')');
  btn.style.width = '100px';
  div.appendChild(btn);

  return div;
};

/**
 *
 */
ArrangePanel.prototype.addGroupOps = function (div) {
  var ui = this.editorUi;
  var graph = ui.editor.graph;
  var cell = graph.getSelectionCell();
  var ss = this.format.getSelectionState();
  var count = 0;
  var btn = null;

  div.style.paddingTop = '8px';
  div.style.paddingBottom = '6px';

  if (graph.getSelectionCount() > 1) {
    btn = mxUtils.button(mxResources.get('group'), function (evt) {
      ui.actions.get('group').funct();
    });

    btn.setAttribute('title', mxResources.get('group') + ' (' + this.editorUi.actions.get('group').shortcut + ')');
    btn.style.width = '202px';
    btn.style.marginBottom = '2px';
    div.appendChild(btn);
    count++;
  } else if (
    graph.getSelectionCount() == 1 &&
    !graph.getModel().isEdge(cell) &&
    !graph.isSwimlane(cell) &&
    !graph.isTable(cell) &&
    !ss.row &&
    !ss.cell &&
    graph.getModel().getChildCount(cell) > 0
  ) {
    btn = mxUtils.button(mxResources.get('ungroup'), function (evt) {
      ui.actions.get('ungroup').funct();
    });

    btn.setAttribute('title', mxResources.get('ungroup') + ' (' + this.editorUi.actions.get('ungroup').shortcut + ')');
    btn.style.width = '202px';
    btn.style.marginBottom = '2px';
    div.appendChild(btn);
    count++;
  }

  if (ss.vertices.length > 0) {
    if (count > 0) {
      mxUtils.br(div);
      count = 0;
    }

    var btn = mxUtils.button(mxResources.get('copySize'), function (evt) {
      ui.actions.get('copySize').funct();
    });

    btn.setAttribute(
      'title',
      mxResources.get('copySize') + ' (' + this.editorUi.actions.get('copySize').shortcut + ')'
    );
    btn.style.width = '202px';
    btn.style.marginBottom = '2px';

    div.appendChild(btn);
    count++;

    if (ui.copiedSize != null) {
      var btn2 = mxUtils.button(mxResources.get('pasteSize'), function (evt) {
        ui.actions.get('pasteSize').funct();
      });

      btn2.setAttribute(
        'title',
        mxResources.get('pasteSize') + ' (' + this.editorUi.actions.get('pasteSize').shortcut + ')'
      );

      div.appendChild(btn2);
      count++;

      btn.style.width = '100px';
      btn.style.marginBottom = '2px';
      btn2.style.width = '100px';
      btn2.style.marginBottom = '2px';
    }
  }

  if (
    graph.getSelectionCount() == 1 &&
    graph.getModel().isVertex(cell) &&
    !ss.row &&
    !ss.cell &&
    graph.getModel().isVertex(graph.getModel().getParent(cell))
  ) {
    if (count > 0) {
      mxUtils.br(div);
    }

    btn = mxUtils.button(mxResources.get('removeFromGroup'), function (evt) {
      ui.actions.get('removeFromGroup').funct();
    });

    btn.setAttribute('title', mxResources.get('removeFromGroup'));
    btn.style.width = '202px';
    btn.style.marginBottom = '2px';
    div.appendChild(btn);
    count++;
  } else if (graph.getSelectionCount() > 0) {
    if (count > 0) {
      mxUtils.br(div);
    }

    btn = mxUtils.button(
      mxResources.get('clearWaypoints'),
      mxUtils.bind(this, function (evt) {
        this.editorUi.actions.get('clearWaypoints').funct();
      })
    );

    btn.setAttribute(
      'title',
      mxResources.get('clearWaypoints') + ' (' + this.editorUi.actions.get('clearWaypoints').shortcut + ')'
    );
    btn.style.width = '202px';
    btn.style.marginBottom = '2px';
    div.appendChild(btn);

    count++;
  }

  if (graph.getSelectionCount() == 1) {
    if (count > 0) {
      mxUtils.br(div);
    }

    btn = mxUtils.button(
      mxResources.get('editData'),
      mxUtils.bind(this, function (evt) {
        this.editorUi.actions.get('editData').funct();
      })
    );

    btn.setAttribute(
      'title',
      mxResources.get('editData') + ' (' + this.editorUi.actions.get('editData').shortcut + ')'
    );
    btn.style.width = '100px';
    btn.style.marginBottom = '2px';
    div.appendChild(btn);
    count++;

    btn = mxUtils.button(
      mxResources.get('editLink'),
      mxUtils.bind(this, function (evt) {
        this.editorUi.actions.get('editLink').funct();
      })
    );

    btn.setAttribute('title', mxResources.get('editLink'));
    btn.style.width = '100px';
    btn.style.marginLeft = '2px';
    btn.style.marginBottom = '2px';
    div.appendChild(btn);
    count++;
  }

  if (count == 0) {
    div.style.display = 'none';
  }

  return div;
};

/**
 *
 */
ArrangePanel.prototype.addAlign = function (div) {
  var graph = this.editorUi.editor.graph;
  div.style.paddingTop = '6px';
  div.style.paddingBottom = '12px';
  div.appendChild(this.createTitle(mxResources.get('align')));

  var stylePanel = document.createElement('div');
  stylePanel.style.position = 'relative';
  stylePanel.style.paddingLeft = '0px';
  stylePanel.style.borderWidth = '0px';
  stylePanel.className = 'geToolbarContainer';

  if (mxClient.IS_QUIRKS) {
    div.style.height = '60px';
  }

  var left = this.editorUi.toolbar.addButton(
    'geSprite-alignleft',
    mxResources.get('left'),
    function () {
      graph.alignCells(mxConstants.ALIGN_LEFT);
    },
    stylePanel
  );
  var center = this.editorUi.toolbar.addButton(
    'geSprite-aligncenter',
    mxResources.get('center'),
    function () {
      graph.alignCells(mxConstants.ALIGN_CENTER);
    },
    stylePanel
  );
  var right = this.editorUi.toolbar.addButton(
    'geSprite-alignright',
    mxResources.get('right'),
    function () {
      graph.alignCells(mxConstants.ALIGN_RIGHT);
    },
    stylePanel
  );

  var top = this.editorUi.toolbar.addButton(
    'geSprite-aligntop',
    mxResources.get('top'),
    function () {
      graph.alignCells(mxConstants.ALIGN_TOP);
    },
    stylePanel
  );
  var middle = this.editorUi.toolbar.addButton(
    'geSprite-alignmiddle',
    mxResources.get('middle'),
    function () {
      graph.alignCells(mxConstants.ALIGN_MIDDLE);
    },
    stylePanel
  );
  var bottom = this.editorUi.toolbar.addButton(
    'geSprite-alignbottom',
    mxResources.get('bottom'),
    function () {
      graph.alignCells(mxConstants.ALIGN_BOTTOM);
    },
    stylePanel
  );

  this.styleButtons([left, center, right, top, middle, bottom]);
  right.style.marginRight = '6px';
  div.appendChild(stylePanel);

  return div;
};

/**
 *
 */
ArrangePanel.prototype.addFlip = function (div) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;
  div.style.paddingTop = '6px';
  div.style.paddingBottom = '10px';

  var span = document.createElement('div');
  span.style.marginTop = '2px';
  span.style.marginBottom = '8px';
  span.style.fontWeight = 'bold';
  mxUtils.write(span, mxResources.get('flip'));
  div.appendChild(span);

  var btn = mxUtils.button(mxResources.get('horizontal'), function (evt) {
    graph.toggleCellStyles(mxConstants.STYLE_FLIPH, false);
  });

  btn.setAttribute('title', mxResources.get('horizontal'));
  btn.style.width = '100px';
  btn.style.marginRight = '2px';
  div.appendChild(btn);

  var btn = mxUtils.button(mxResources.get('vertical'), function (evt) {
    graph.toggleCellStyles(mxConstants.STYLE_FLIPV, false);
  });

  btn.setAttribute('title', mxResources.get('vertical'));
  btn.style.width = '100px';
  div.appendChild(btn);

  return div;
};

/**
 *
 */
ArrangePanel.prototype.addDistribute = function (div) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;
  div.style.paddingTop = '6px';
  div.style.paddingBottom = '12px';

  div.appendChild(this.createTitle(mxResources.get('distribute')));

  var btn = mxUtils.button(mxResources.get('horizontal'), function (evt) {
    graph.distributeCells(true);
  });

  btn.setAttribute('title', mxResources.get('horizontal'));
  btn.style.width = '100px';
  btn.style.marginRight = '2px';
  div.appendChild(btn);

  var btn = mxUtils.button(mxResources.get('vertical'), function (evt) {
    graph.distributeCells(false);
  });

  btn.setAttribute('title', mxResources.get('vertical'));
  btn.style.width = '100px';
  div.appendChild(btn);

  return div;
};

/**
 *
 */
ArrangePanel.prototype.addAngle = function (div) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;
  var ss = this.format.getSelectionState();

  div.style.paddingBottom = '8px';

  var span = document.createElement('div');
  span.style.position = 'absolute';
  span.style.width = '70px';
  span.style.marginTop = '0px';
  span.style.fontWeight = 'bold';

  var input = null;
  var update = null;
  var btn = null;

  if (ss.rotatable && !ss.table && !ss.row && !ss.cell) {
    mxUtils.write(span, mxResources.get('angle'));
    div.appendChild(span);

    input = this.addUnitInput(div, '°', 20, 44, function () {
      update.apply(this, arguments);
    });

    mxUtils.br(div);
    div.style.paddingTop = '10px';
  } else {
    div.style.paddingTop = '8px';
  }

  if (!ss.containsLabel) {
    var label = mxResources.get('reverse');

    if (ss.vertices.length > 0 && ss.edges.length > 0) {
      label = mxResources.get('turn') + ' / ' + label;
    } else if (ss.vertices.length > 0) {
      label = mxResources.get('turn');
    }

    btn = mxUtils.button(label, function (evt) {
      ui.actions.get('turn').funct(evt);
    });

    btn.setAttribute('title', label + ' (' + this.editorUi.actions.get('turn').shortcut + ')');
    btn.style.width = '202px';
    div.appendChild(btn);

    if (input != null) {
      btn.style.marginTop = '8px';
    }
  }

  if (input != null) {
    var listener = mxUtils.bind(this, function (sender, evt, force) {
      if (force || document.activeElement != input) {
        ss = this.format.getSelectionState();
        var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_ROTATION, 0));
        input.value = isNaN(tmp) ? '' : tmp + '°';
      }
    });

    update = this.installInputHandler(input, mxConstants.STYLE_ROTATION, 0, 0, 360, '°', null, true);
    this.addKeyHandler(input, listener);

    graph.getModel().addListener(mxEvent.CHANGE, listener);
    this.listeners.push({
      destroy: function () {
        graph.getModel().removeListener(listener);
      },
    });
    listener();
  }

  return div;
};

BaseFormatPanel.prototype.getUnit = function () {
  var unit = this.editorUi.editor.graph.view.unit;

  switch (unit) {
    case mxConstants.POINTS:
      return 'pt';
    case mxConstants.INCHES:
      return '"';
    case mxConstants.MILLIMETERS:
      return 'mm';
  }
};

BaseFormatPanel.prototype.inUnit = function (pixels) {
  return this.editorUi.editor.graph.view.formatUnitText(pixels);
};

BaseFormatPanel.prototype.fromUnit = function (value) {
  var unit = this.editorUi.editor.graph.view.unit;

  switch (unit) {
    case mxConstants.POINTS:
      return value;
    case mxConstants.INCHES:
      return value * mxConstants.PIXELS_PER_INCH;
    case mxConstants.MILLIMETERS:
      return value * mxConstants.PIXELS_PER_MM;
  }
};

BaseFormatPanel.prototype.isFloatUnit = function () {
  return this.editorUi.editor.graph.view.unit != mxConstants.POINTS;
};

BaseFormatPanel.prototype.getUnitStep = function () {
  var unit = this.editorUi.editor.graph.view.unit;

  switch (unit) {
    case mxConstants.POINTS:
      return 1;
    case mxConstants.INCHES:
      return 0.1;
    case mxConstants.MILLIMETERS:
      return 0.5;
  }
};

/**
 *
 */
ArrangePanel.prototype.addGeometry = function (container) {
  var panel = this;
  var ui = this.editorUi;
  var graph = ui.editor.graph;
  var rect = this.format.getSelectionState();

  var div = this.createPanel();
  div.style.paddingBottom = '8px';

  var span = document.createElement('div');
  span.style.position = 'absolute';
  span.style.width = '50px';
  span.style.marginTop = '0px';
  span.style.fontWeight = 'bold';
  mxUtils.write(span, mxResources.get('size'));
  div.appendChild(span);

  var widthUpdate, heightUpdate, leftUpdate, topUpdate;
  var width = this.addUnitInput(
    div,
    this.getUnit(),
    84,
    44,
    function () {
      widthUpdate.apply(this, arguments);
    },
    this.getUnitStep(),
    null,
    null,
    this.isFloatUnit()
  );
  var height = this.addUnitInput(
    div,
    this.getUnit(),
    20,
    44,
    function () {
      heightUpdate.apply(this, arguments);
    },
    this.getUnitStep(),
    null,
    null,
    this.isFloatUnit()
  );

  var autosizeBtn = document.createElement('div');
  autosizeBtn.className = 'geSprite geSprite-fit';
  autosizeBtn.setAttribute(
    'title',
    mxResources.get('autosize') + ' (' + this.editorUi.actions.get('autosize').shortcut + ')'
  );
  autosizeBtn.style.position = 'relative';
  autosizeBtn.style.cursor = 'pointer';
  autosizeBtn.style.marginTop = '-3px';
  autosizeBtn.style.border = '0px';
  autosizeBtn.style.left = '42px';
  mxUtils.setOpacity(autosizeBtn, 50);

  mxEvent.addListener(autosizeBtn, 'mouseenter', function () {
    mxUtils.setOpacity(autosizeBtn, 100);
  });

  mxEvent.addListener(autosizeBtn, 'mouseleave', function () {
    mxUtils.setOpacity(autosizeBtn, 50);
  });

  mxEvent.addListener(autosizeBtn, 'click', function () {
    ui.actions.get('autosize').funct();
  });

  div.appendChild(autosizeBtn);

  if (rect.row) {
    width.style.visibility = 'hidden';
    width.nextSibling.style.visibility = 'hidden';
  } else {
    this.addLabel(div, mxResources.get('width'), 84);
  }

  this.addLabel(div, mxResources.get('height'), 20);
  mxUtils.br(div);

  var wrapper = document.createElement('div');
  wrapper.style.paddingTop = '8px';
  wrapper.style.paddingRight = '20px';
  wrapper.style.whiteSpace = 'nowrap';
  wrapper.style.textAlign = 'right';
  var opt = this.createCellOption(
    mxResources.get('constrainProportions'),
    mxConstants.STYLE_ASPECT,
    null,
    'fixed',
    'null'
  );
  opt.style.width = '100%';
  wrapper.appendChild(opt);

  if (!rect.cell && !rect.row) {
    div.appendChild(wrapper);
  } else {
    autosizeBtn.style.visibility = 'hidden';
  }

  var constrainCheckbox = opt.getElementsByTagName('input')[0];
  this.addKeyHandler(width, listener);
  this.addKeyHandler(height, listener);

  widthUpdate = this.addGeometryHandler(width, function (geo, value, cell) {
    if (graph.isTableCell(cell)) {
      graph.setTableColumnWidth(cell, value - geo.width, true);

      // Blocks processing in caller
      return true;
    } else if (geo.width > 0) {
      var value = Math.max(1, panel.fromUnit(value));

      if (constrainCheckbox.checked) {
        geo.height = Math.round((geo.height * value * 100) / geo.width) / 100;
      }

      geo.width = value;
    }
  });
  heightUpdate = this.addGeometryHandler(height, function (geo, value, cell) {
    if (graph.isTableCell(cell)) {
      cell = graph.model.getParent(cell);
    }

    if (graph.isTableRow(cell)) {
      graph.setTableRowHeight(cell, value - geo.height);

      // Blocks processing in caller
      return true;
    } else if (geo.height > 0) {
      var value = Math.max(1, panel.fromUnit(value));

      if (constrainCheckbox.checked) {
        geo.width = Math.round((geo.width * value * 100) / geo.height) / 100;
      }

      geo.height = value;
    }
  });

  if (rect.resizable || rect.row || rect.cell) {
    container.appendChild(div);
  }

  var div2 = this.createPanel();
  div2.style.paddingBottom = '30px';

  var span = document.createElement('div');
  span.style.position = 'absolute';
  span.style.width = '70px';
  span.style.marginTop = '0px';
  span.style.fontWeight = 'bold';
  mxUtils.write(span, mxResources.get('position'));
  div2.appendChild(span);

  var left = this.addUnitInput(
    div2,
    this.getUnit(),
    84,
    44,
    function () {
      leftUpdate.apply(this, arguments);
    },
    this.getUnitStep(),
    null,
    null,
    this.isFloatUnit()
  );
  var top = this.addUnitInput(
    div2,
    this.getUnit(),
    20,
    44,
    function () {
      topUpdate.apply(this, arguments);
    },
    this.getUnitStep(),
    null,
    null,
    this.isFloatUnit()
  );

  mxUtils.br(div2);

  this.addLabel(div2, mxResources.get('left'), 84);
  this.addLabel(div2, mxResources.get('top'), 20);

  var listener = mxUtils.bind(this, function (sender, evt, force) {
    rect = this.format.getSelectionState();

    if (
      !rect.containsLabel &&
      rect.vertices.length == graph.getSelectionCount() &&
      rect.width != null &&
      rect.height != null
    ) {
      div.style.display = '';

      if (force || document.activeElement != width) {
        width.value = this.inUnit(rect.width) + (rect.width == '' ? '' : ' ' + this.getUnit());
      }

      if (force || document.activeElement != height) {
        height.value = this.inUnit(rect.height) + (rect.height == '' ? '' : ' ' + this.getUnit());
      }
    } else {
      div.style.display = 'none';
    }

    if (rect.vertices.length == graph.getSelectionCount() && rect.x != null && rect.y != null) {
      div2.style.display = '';

      if (force || document.activeElement != left) {
        left.value = this.inUnit(rect.x) + (rect.x == '' ? '' : ' ' + this.getUnit());
      }

      if (force || document.activeElement != top) {
        top.value = this.inUnit(rect.y) + (rect.y == '' ? '' : ' ' + this.getUnit());
      }
    } else {
      div2.style.display = 'none';
    }
  });

  this.addKeyHandler(left, listener);
  this.addKeyHandler(top, listener);

  graph.getModel().addListener(mxEvent.CHANGE, listener);
  this.listeners.push({
    destroy: function () {
      graph.getModel().removeListener(listener);
    },
  });
  listener();

  leftUpdate = this.addGeometryHandler(left, function (geo, value) {
    value = panel.fromUnit(value);

    if (geo.relative) {
      geo.offset.x = value;
    } else {
      geo.x = value;
    }
  });
  topUpdate = this.addGeometryHandler(top, function (geo, value) {
    value = panel.fromUnit(value);

    if (geo.relative) {
      geo.offset.y = value;
    } else {
      geo.y = value;
    }
  });

  if (rect.movable) {
    container.appendChild(div2);
  }
};

/**
 *
 */
ArrangePanel.prototype.addGeometryHandler = function (input, fn) {
  var ui = this.editorUi;
  var graph = ui.editor.graph;
  var initialValue = null;
  var panel = this;

  function update(evt) {
    if (input.value != '') {
      var value = parseFloat(input.value);

      if (isNaN(value)) {
        input.value = initialValue + ' ' + panel.getUnit();
      } else if (value != initialValue) {
        graph.getModel().beginUpdate();
        try {
          var cells = graph.getSelectionCells();

          for (var i = 0; i < cells.length; i++) {
            if (graph.getModel().isVertex(cells[i])) {
              var geo = graph.getCellGeometry(cells[i]);

              if (geo != null) {
                geo = geo.clone();

                if (!fn(geo, value, cells[i])) {
                  var state = graph.view.getState(cells[i]);

                  if (state != null && graph.isRecursiveVertexResize(state)) {
                    graph.resizeChildCells(cells[i], geo);
                  }

                  graph.getModel().setGeometry(cells[i], geo);
                  graph.constrainChildCells(cells[i]);
                }
              }
            }
          }
        } finally {
          graph.getModel().endUpdate();
        }

        initialValue = value;
        input.value = value + ' ' + panel.getUnit();
      }
    }

    mxEvent.consume(evt);
  }

  mxEvent.addListener(input, 'blur', update);
  mxEvent.addListener(input, 'change', update);
  mxEvent.addListener(input, 'focus', function () {
    initialValue = input.value;
  });

  return update;
};

ArrangePanel.prototype.addEdgeGeometryHandler = function (input, fn) {
  var ui = this.editorUi;
  var graph = ui.editor.graph;
  var initialValue = null;

  function update(evt) {
    if (input.value != '') {
      var value = parseFloat(input.value);

      if (isNaN(value)) {
        input.value = initialValue + ' pt';
      } else if (value != initialValue) {
        graph.getModel().beginUpdate();
        try {
          var cells = graph.getSelectionCells();

          for (var i = 0; i < cells.length; i++) {
            if (graph.getModel().isEdge(cells[i])) {
              var geo = graph.getCellGeometry(cells[i]);

              if (geo != null) {
                geo = geo.clone();
                fn(geo, value);

                graph.getModel().setGeometry(cells[i], geo);
              }
            }
          }
        } finally {
          graph.getModel().endUpdate();
        }

        initialValue = value;
        input.value = value + ' pt';
      }
    }

    mxEvent.consume(evt);
  }

  mxEvent.addListener(input, 'blur', update);
  mxEvent.addListener(input, 'change', update);
  mxEvent.addListener(input, 'focus', function () {
    initialValue = input.value;
  });

  return update;
};

/**
 *
 */
ArrangePanel.prototype.addEdgeGeometry = function (container) {
  var ui = this.editorUi;
  var graph = ui.editor.graph;
  var rect = this.format.getSelectionState();

  var div = this.createPanel();

  var span = document.createElement('div');
  span.style.position = 'absolute';
  span.style.width = '70px';
  span.style.marginTop = '0px';
  span.style.fontWeight = 'bold';
  mxUtils.write(span, mxResources.get('width'));
  div.appendChild(span);

  var widthUpdate, xtUpdate, ytUpdate, xsUpdate, ysUpdate;
  var width = this.addUnitInput(div, 'pt', 20, 44, function () {
    widthUpdate.apply(this, arguments);
  });

  mxUtils.br(div);
  this.addKeyHandler(width, listener);

  function widthUpdate(evt) {
    // Maximum stroke width is 999
    var value = parseInt(width.value);
    value = Math.min(999, Math.max(1, isNaN(value) ? 1 : value));

    if (
      value != mxUtils.getValue(rect.style, 'width', mxCellRenderer.defaultShapes['flexArrow'].prototype.defaultWidth)
    ) {
      graph.setCellStyles('width', value, graph.getSelectionCells());
      ui.fireEvent(
        new mxEventObject('styleChanged', 'keys', ['width'], 'values', [value], 'cells', graph.getSelectionCells())
      );
    }

    width.value = value + ' pt';
    mxEvent.consume(evt);
  }

  mxEvent.addListener(width, 'blur', widthUpdate);
  mxEvent.addListener(width, 'change', widthUpdate);

  container.appendChild(div);

  var divs = this.createPanel();
  divs.style.paddingBottom = '30px';

  var span = document.createElement('div');
  span.style.position = 'absolute';
  span.style.width = '70px';
  span.style.marginTop = '0px';
  span.style.fontWeight = 'bold';
  mxUtils.write(span, 'Start');
  divs.appendChild(span);

  var xs = this.addUnitInput(divs, 'pt', 84, 44, function () {
    xsUpdate.apply(this, arguments);
  });
  var ys = this.addUnitInput(divs, 'pt', 20, 44, function () {
    ysUpdate.apply(this, arguments);
  });

  mxUtils.br(divs);
  this.addLabel(divs, mxResources.get('left'), 84);
  this.addLabel(divs, mxResources.get('top'), 20);
  container.appendChild(divs);
  this.addKeyHandler(xs, listener);
  this.addKeyHandler(ys, listener);

  var divt = this.createPanel();
  divt.style.paddingBottom = '30px';

  var span = document.createElement('div');
  span.style.position = 'absolute';
  span.style.width = '70px';
  span.style.marginTop = '0px';
  span.style.fontWeight = 'bold';
  mxUtils.write(span, 'End');
  divt.appendChild(span);

  var xt = this.addUnitInput(divt, 'pt', 84, 44, function () {
    xtUpdate.apply(this, arguments);
  });
  var yt = this.addUnitInput(divt, 'pt', 20, 44, function () {
    ytUpdate.apply(this, arguments);
  });

  mxUtils.br(divt);
  this.addLabel(divt, mxResources.get('left'), 84);
  this.addLabel(divt, mxResources.get('top'), 20);
  container.appendChild(divt);
  this.addKeyHandler(xt, listener);
  this.addKeyHandler(yt, listener);

  var listener = mxUtils.bind(this, function (sender, evt, force) {
    rect = this.format.getSelectionState();
    var cell = graph.getSelectionCell();

    if (rect.style.shape == 'link' || rect.style.shape == 'flexArrow') {
      div.style.display = '';

      if (force || document.activeElement != width) {
        var value = mxUtils.getValue(
          rect.style,
          'width',
          mxCellRenderer.defaultShapes['flexArrow'].prototype.defaultWidth
        );
        width.value = value + ' pt';
      }
    } else {
      div.style.display = 'none';
    }

    if (graph.getSelectionCount() == 1 && graph.model.isEdge(cell)) {
      var geo = graph.model.getGeometry(cell);

      if (geo.sourcePoint != null && graph.model.getTerminal(cell, true) == null) {
        xs.value = geo.sourcePoint.x;
        ys.value = geo.sourcePoint.y;
      } else {
        divs.style.display = 'none';
      }

      if (geo.targetPoint != null && graph.model.getTerminal(cell, false) == null) {
        xt.value = geo.targetPoint.x;
        yt.value = geo.targetPoint.y;
      } else {
        divt.style.display = 'none';
      }
    } else {
      divs.style.display = 'none';
      divt.style.display = 'none';
    }
  });

  xsUpdate = this.addEdgeGeometryHandler(xs, function (geo, value) {
    geo.sourcePoint.x = value;
  });

  ysUpdate = this.addEdgeGeometryHandler(ys, function (geo, value) {
    geo.sourcePoint.y = value;
  });

  xtUpdate = this.addEdgeGeometryHandler(xt, function (geo, value) {
    geo.targetPoint.x = value;
  });

  ytUpdate = this.addEdgeGeometryHandler(yt, function (geo, value) {
    geo.targetPoint.y = value;
  });

  graph.getModel().addListener(mxEvent.CHANGE, listener);
  this.listeners.push({
    destroy: function () {
      graph.getModel().removeListener(listener);
    },
  });
  listener();
};

/**
 * Adds the label menu items to the given menu and parent.
 */
TextFormatPanel = function (format, editorUi, container) {
  BaseFormatPanel.call(this, format, editorUi, container);
  this.init();
};

mxUtils.extend(TextFormatPanel, BaseFormatPanel);

/**
 * Adds the label menu items to the given menu and parent.
 */
TextFormatPanel.prototype.init = function () {
  this.container.style.borderBottom = 'none';
  this.addFont(this.container);
};

/**
 * Adds the label menu items to the given menu and parent.
 */
TextFormatPanel.prototype.addFont = function (container) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;
  var ss = this.format.getSelectionState();

  var title = this.createTitle(mxResources.get('font'));
  title.style.paddingLeft = '18px';
  title.style.paddingTop = '10px';
  title.style.paddingBottom = '6px';
  container.appendChild(title);

  var stylePanel = this.createPanel();
  stylePanel.style.paddingTop = '2px';
  stylePanel.style.paddingBottom = '2px';
  stylePanel.style.position = 'relative';
  stylePanel.style.marginLeft = '-2px';
  stylePanel.style.borderWidth = '0px';
  stylePanel.className = 'geToolbarContainer';

  if (mxClient.IS_QUIRKS) {
    stylePanel.style.display = 'block';
  }

  if (graph.cellEditor.isContentEditing()) {
    var cssPanel = stylePanel.cloneNode();

    var cssMenu = this.editorUi.toolbar.addMenu(
      mxResources.get('style'),
      mxResources.get('style'),
      true,
      'formatBlock',
      cssPanel,
      null,
      true
    );
    cssMenu.style.color = 'rgb(112, 112, 112)';
    cssMenu.style.whiteSpace = 'nowrap';
    cssMenu.style.overflow = 'hidden';
    cssMenu.style.margin = '0px';
    this.addArrow(cssMenu);
    cssMenu.style.width = '192px';
    cssMenu.style.height = '15px';

    var arrow = cssMenu.getElementsByTagName('div')[0];
    arrow.style.cssFloat = 'right';
    container.appendChild(cssPanel);
  }

  container.appendChild(stylePanel);

  var colorPanel = this.createPanel();
  colorPanel.style.marginTop = '8px';
  colorPanel.style.borderTop = '1px solid #c0c0c0';
  colorPanel.style.paddingTop = '6px';
  colorPanel.style.paddingBottom = '6px';

  var fontMenu = this.editorUi.toolbar.addMenu(
    'Helvetica',
    mxResources.get('fontFamily'),
    true,
    'fontFamily',
    stylePanel,
    null,
    true
  );
  fontMenu.style.color = 'rgb(112, 112, 112)';
  fontMenu.style.whiteSpace = 'nowrap';
  fontMenu.style.overflow = 'hidden';
  fontMenu.style.margin = '0px';

  this.addArrow(fontMenu);
  fontMenu.style.width = '192px';
  fontMenu.style.height = '15px';

  var stylePanel2 = stylePanel.cloneNode(false);
  stylePanel2.style.marginLeft = '-3px';
  var fontStyleItems = this.editorUi.toolbar.addItems(['bold', 'italic', 'underline'], stylePanel2, true);
  fontStyleItems[0].setAttribute(
    'title',
    mxResources.get('bold') + ' (' + this.editorUi.actions.get('bold').shortcut + ')'
  );
  fontStyleItems[1].setAttribute(
    'title',
    mxResources.get('italic') + ' (' + this.editorUi.actions.get('italic').shortcut + ')'
  );
  fontStyleItems[2].setAttribute(
    'title',
    mxResources.get('underline') + ' (' + this.editorUi.actions.get('underline').shortcut + ')'
  );

  var verticalItem = this.editorUi.toolbar.addItems(['vertical'], stylePanel2, true)[0];

  if (mxClient.IS_QUIRKS) {
    mxUtils.br(container);
  }

  container.appendChild(stylePanel2);

  this.styleButtons(fontStyleItems);
  this.styleButtons([verticalItem]);

  var stylePanel3 = stylePanel.cloneNode(false);
  stylePanel3.style.marginLeft = '-3px';
  stylePanel3.style.paddingBottom = '0px';

  // Helper function to return a wrapper function does not pass any arguments
  var callFn = function (fn) {
    return function () {
      return fn();
    };
  };

  var left = this.editorUi.toolbar.addButton(
    'geSprite-left',
    mxResources.get('left'),
    graph.cellEditor.isContentEditing()
      ? function (evt) {
          graph.cellEditor.alignText(mxConstants.ALIGN_LEFT, evt);
        }
      : callFn(this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_LEFT])),
    stylePanel3
  );
  var center = this.editorUi.toolbar.addButton(
    'geSprite-center',
    mxResources.get('center'),
    graph.cellEditor.isContentEditing()
      ? function (evt) {
          graph.cellEditor.alignText(mxConstants.ALIGN_CENTER, evt);
        }
      : callFn(this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_CENTER])),
    stylePanel3
  );
  var right = this.editorUi.toolbar.addButton(
    'geSprite-right',
    mxResources.get('right'),
    graph.cellEditor.isContentEditing()
      ? function (evt) {
          graph.cellEditor.alignText(mxConstants.ALIGN_RIGHT, evt);
        }
      : callFn(this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_ALIGN], [mxConstants.ALIGN_RIGHT])),
    stylePanel3
  );

  this.styleButtons([left, center, right]);

  // Quick hack for strikethrough
  // TODO: Add translations and toggle state
  if (graph.cellEditor.isContentEditing()) {
    var strike = this.editorUi.toolbar.addButton(
      'geSprite-removeformat',
      mxResources.get('strikethrough'),
      function () {
        document.execCommand('strikeThrough', false, null);
      },
      stylePanel2
    );
    this.styleButtons([strike]);

    strike.firstChild.style.background =
      'url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGRlZnM+PHBhdGggaWQ9ImEiIGQ9Ik0wIDBoMjR2MjRIMFYweiIvPjwvZGVmcz48Y2xpcFBhdGggaWQ9ImIiPjx1c2UgeGxpbms6aHJlZj0iI2EiIG92ZXJmbG93PSJ2aXNpYmxlIi8+PC9jbGlwUGF0aD48cGF0aCBjbGlwLXBhdGg9InVybCgjYikiIGZpbGw9IiMwMTAxMDEiIGQ9Ik03LjI0IDguNzVjLS4yNi0uNDgtLjM5LTEuMDMtLjM5LTEuNjcgMC0uNjEuMTMtMS4xNi40LTEuNjcuMjYtLjUuNjMtLjkzIDEuMTEtMS4yOS40OC0uMzUgMS4wNS0uNjMgMS43LS44My42Ni0uMTkgMS4zOS0uMjkgMi4xOC0uMjkuODEgMCAxLjU0LjExIDIuMjEuMzQuNjYuMjIgMS4yMy41NCAxLjY5Ljk0LjQ3LjQuODMuODggMS4wOCAxLjQzLjI1LjU1LjM4IDEuMTUuMzggMS44MWgtMy4wMWMwLS4zMS0uMDUtLjU5LS4xNS0uODUtLjA5LS4yNy0uMjQtLjQ5LS40NC0uNjgtLjItLjE5LS40NS0uMzMtLjc1LS40NC0uMy0uMS0uNjYtLjE2LTEuMDYtLjE2LS4zOSAwLS43NC4wNC0xLjAzLjEzLS4yOS4wOS0uNTMuMjEtLjcyLjM2LS4xOS4xNi0uMzQuMzQtLjQ0LjU1LS4xLjIxLS4xNS40My0uMTUuNjYgMCAuNDguMjUuODguNzQgMS4yMS4zOC4yNS43Ny40OCAxLjQxLjdINy4zOWMtLjA1LS4wOC0uMTEtLjE3LS4xNS0uMjV6TTIxIDEydi0ySDN2Mmg5LjYyYy4xOC4wNy40LjE0LjU1LjIuMzcuMTcuNjYuMzQuODcuNTEuMjEuMTcuMzUuMzYuNDMuNTcuMDcuMi4xMS40My4xMS42OSAwIC4yMy0uMDUuNDUtLjE0LjY2LS4wOS4yLS4yMy4zOC0uNDIuNTMtLjE5LjE1LS40Mi4yNi0uNzEuMzUtLjI5LjA4LS42My4xMy0xLjAxLjEzLS40MyAwLS44My0uMDQtMS4xOC0uMTNzLS42Ni0uMjMtLjkxLS40MmMtLjI1LS4xOS0uNDUtLjQ0LS41OS0uNzUtLjE0LS4zMS0uMjUtLjc2LS4yNS0xLjIxSDYuNGMwIC41NS4wOCAxLjEzLjI0IDEuNTguMTYuNDUuMzcuODUuNjUgMS4yMS4yOC4zNS42LjY2Ljk4LjkyLjM3LjI2Ljc4LjQ4IDEuMjIuNjUuNDQuMTcuOS4zIDEuMzguMzkuNDguMDguOTYuMTMgMS40NC4xMy44IDAgMS41My0uMDkgMi4xOC0uMjhzMS4yMS0uNDUgMS42Ny0uNzljLjQ2LS4zNC44Mi0uNzcgMS4wNy0xLjI3cy4zOC0xLjA3LjM4LTEuNzFjMC0uNi0uMS0xLjE0LS4zMS0xLjYxLS4wNS0uMTEtLjExLS4yMy0uMTctLjMzSDIxeiIvPjwvc3ZnPg==)';
    strike.firstChild.style.backgroundPosition = '2px 2px';
    strike.firstChild.style.backgroundSize = '18px 18px';

    this.styleButtons([strike]);
  }

  var top = this.editorUi.toolbar.addButton(
    'geSprite-top',
    mxResources.get('top'),
    callFn(this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_VERTICAL_ALIGN], [mxConstants.ALIGN_TOP])),
    stylePanel3
  );
  var middle = this.editorUi.toolbar.addButton(
    'geSprite-middle',
    mxResources.get('middle'),
    callFn(
      this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_VERTICAL_ALIGN], [mxConstants.ALIGN_MIDDLE])
    ),
    stylePanel3
  );
  var bottom = this.editorUi.toolbar.addButton(
    'geSprite-bottom',
    mxResources.get('bottom'),
    callFn(
      this.editorUi.menus.createStyleChangeFunction([mxConstants.STYLE_VERTICAL_ALIGN], [mxConstants.ALIGN_BOTTOM])
    ),
    stylePanel3
  );

  this.styleButtons([top, middle, bottom]);

  if (mxClient.IS_QUIRKS) {
    mxUtils.br(container);
  }

  container.appendChild(stylePanel3);

  // Hack for updating UI state below based on current text selection
  // currentTable is the current selected DOM table updated below
  var sub, sup, full, tableWrapper, currentTable, tableCell, tableRow;

  if (graph.cellEditor.isContentEditing()) {
    top.style.display = 'none';
    middle.style.display = 'none';
    bottom.style.display = 'none';
    verticalItem.style.display = 'none';

    full = this.editorUi.toolbar.addButton(
      'geSprite-justifyfull',
      mxResources.get('block'),
      function () {
        if (full.style.opacity == 1) {
          document.execCommand('justifyfull', false, null);
        }
      },
      stylePanel3
    );
    full.style.marginRight = '9px';
    full.style.opacity = 1;

    this.styleButtons([
      full,
      (sub = this.editorUi.toolbar.addButton(
        'geSprite-subscript',
        mxResources.get('subscript') + ' (' + Editor.ctrlKey + '+,)',
        function () {
          document.execCommand('subscript', false, null);
        },
        stylePanel3
      )),
      (sup = this.editorUi.toolbar.addButton(
        'geSprite-superscript',
        mxResources.get('superscript') + ' (' + Editor.ctrlKey + '+.)',
        function () {
          document.execCommand('superscript', false, null);
        },
        stylePanel3
      )),
    ]);
    sub.style.marginLeft = '9px';

    var tmp = stylePanel3.cloneNode(false);
    tmp.style.paddingTop = '4px';
    var btns = [
      this.editorUi.toolbar.addButton(
        'geSprite-orderedlist',
        mxResources.get('numberedList'),
        function () {
          document.execCommand('insertorderedlist', false, null);
        },
        tmp
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-unorderedlist',
        mxResources.get('bulletedList'),
        function () {
          document.execCommand('insertunorderedlist', false, null);
        },
        tmp
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-outdent',
        mxResources.get('decreaseIndent'),
        function () {
          document.execCommand('outdent', false, null);
        },
        tmp
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-indent',
        mxResources.get('increaseIndent'),
        function () {
          document.execCommand('indent', false, null);
        },
        tmp
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-removeformat',
        mxResources.get('removeFormat'),
        function () {
          document.execCommand('removeformat', false, null);
        },
        tmp
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-code',
        mxResources.get('html'),
        function () {
          graph.cellEditor.toggleViewMode();
        },
        tmp
      ),
    ];
    this.styleButtons(btns);
    btns[btns.length - 2].style.marginLeft = '9px';

    if (mxClient.IS_QUIRKS) {
      mxUtils.br(container);
      tmp.style.height = '40';
    }

    container.appendChild(tmp);
  } else {
    fontStyleItems[2].style.marginRight = '9px';
    right.style.marginRight = '9px';
  }

  // Label position
  var stylePanel4 = stylePanel.cloneNode(false);
  stylePanel4.style.marginLeft = '0px';
  stylePanel4.style.paddingTop = '8px';
  stylePanel4.style.paddingBottom = '4px';
  stylePanel4.style.fontWeight = 'normal';

  mxUtils.write(stylePanel4, mxResources.get('position'));

  // Adds label position options
  var positionSelect = document.createElement('select');
  positionSelect.style.position = 'absolute';
  positionSelect.style.right = '20px';
  positionSelect.style.width = '97px';
  positionSelect.style.marginTop = '-2px';

  var directions = ['topLeft', 'top', 'topRight', 'left', 'center', 'right', 'bottomLeft', 'bottom', 'bottomRight'];
  var lset = {
    topLeft: [mxConstants.ALIGN_LEFT, mxConstants.ALIGN_TOP, mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_BOTTOM],
    top: [mxConstants.ALIGN_CENTER, mxConstants.ALIGN_TOP, mxConstants.ALIGN_CENTER, mxConstants.ALIGN_BOTTOM],
    topRight: [mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_TOP, mxConstants.ALIGN_LEFT, mxConstants.ALIGN_BOTTOM],
    left: [mxConstants.ALIGN_LEFT, mxConstants.ALIGN_MIDDLE, mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_MIDDLE],
    center: [mxConstants.ALIGN_CENTER, mxConstants.ALIGN_MIDDLE, mxConstants.ALIGN_CENTER, mxConstants.ALIGN_MIDDLE],
    right: [mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_MIDDLE, mxConstants.ALIGN_LEFT, mxConstants.ALIGN_MIDDLE],
    bottomLeft: [mxConstants.ALIGN_LEFT, mxConstants.ALIGN_BOTTOM, mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_TOP],
    bottom: [mxConstants.ALIGN_CENTER, mxConstants.ALIGN_BOTTOM, mxConstants.ALIGN_CENTER, mxConstants.ALIGN_TOP],
    bottomRight: [mxConstants.ALIGN_RIGHT, mxConstants.ALIGN_BOTTOM, mxConstants.ALIGN_LEFT, mxConstants.ALIGN_TOP],
  };

  for (var i = 0; i < directions.length; i++) {
    var positionOption = document.createElement('option');
    positionOption.setAttribute('value', directions[i]);
    mxUtils.write(positionOption, mxResources.get(directions[i]));
    positionSelect.appendChild(positionOption);
  }

  stylePanel4.appendChild(positionSelect);

  // Writing direction
  var stylePanel5 = stylePanel.cloneNode(false);
  stylePanel5.style.marginLeft = '0px';
  stylePanel5.style.paddingTop = '4px';
  stylePanel5.style.paddingBottom = '4px';
  stylePanel5.style.fontWeight = 'normal';

  mxUtils.write(stylePanel5, mxResources.get('writingDirection'));

  // Adds writing direction options
  // LATER: Handle reselect of same option in all selects (change event
  // is not fired for same option so have opened state on click) and
  // handle multiple different styles for current selection
  var dirSelect = document.createElement('select');
  dirSelect.style.position = 'absolute';
  dirSelect.style.right = '20px';
  dirSelect.style.width = '97px';
  dirSelect.style.marginTop = '-2px';

  // NOTE: For automatic we use the value null since automatic
  // requires the text to be non formatted and non-wrapped
  var dirs = ['automatic', 'leftToRight', 'rightToLeft'];
  var dirSet = {
    automatic: null,
    leftToRight: mxConstants.TEXT_DIRECTION_LTR,
    rightToLeft: mxConstants.TEXT_DIRECTION_RTL,
  };

  for (var i = 0; i < dirs.length; i++) {
    var dirOption = document.createElement('option');
    dirOption.setAttribute('value', dirs[i]);
    mxUtils.write(dirOption, mxResources.get(dirs[i]));
    dirSelect.appendChild(dirOption);
  }

  stylePanel5.appendChild(dirSelect);

  if (!graph.isEditing()) {
    container.appendChild(stylePanel4);

    mxEvent.addListener(positionSelect, 'change', function (evt) {
      graph.getModel().beginUpdate();
      try {
        var vals = lset[positionSelect.value];

        if (vals != null) {
          graph.setCellStyles(mxConstants.STYLE_LABEL_POSITION, vals[0], graph.getSelectionCells());
          graph.setCellStyles(mxConstants.STYLE_VERTICAL_LABEL_POSITION, vals[1], graph.getSelectionCells());
          graph.setCellStyles(mxConstants.STYLE_ALIGN, vals[2], graph.getSelectionCells());
          graph.setCellStyles(mxConstants.STYLE_VERTICAL_ALIGN, vals[3], graph.getSelectionCells());
        }
      } finally {
        graph.getModel().endUpdate();
      }

      mxEvent.consume(evt);
    });

    // LATER: Update dir in text editor while editing and update style with label
    // NOTE: The tricky part is handling and passing on the auto value
    container.appendChild(stylePanel5);

    mxEvent.addListener(dirSelect, 'change', function (evt) {
      graph.setCellStyles(mxConstants.STYLE_TEXT_DIRECTION, dirSet[dirSelect.value], graph.getSelectionCells());
      mxEvent.consume(evt);
    });
  }

  // Font size
  var input = document.createElement('input');
  input.style.textAlign = 'right';
  input.style.marginTop = '4px';

  if (!mxClient.IS_QUIRKS) {
    input.style.position = 'absolute';
    input.style.right = '32px';
  }

  input.style.width = '40px';
  input.style.height = mxClient.IS_QUIRKS ? '21px' : '17px';
  stylePanel2.appendChild(input);

  // Workaround for font size 4 if no text is selected is update font size below
  // after first character was entered (as the font element is lazy created)
  var pendingFontSize = null;

  var inputUpdate = this.installInputHandler(
    input,
    mxConstants.STYLE_FONTSIZE,
    Menus.prototype.defaultFontSize,
    1,
    999,
    ' pt',
    function (fontSize) {
      // IE does not support containsNode
      // KNOWN: Fixes font size issues but bypasses undo
      if (window.getSelection && !mxClient.IS_IE && !mxClient.IS_IE11) {
        var selection = window.getSelection();
        var container =
          selection.rangeCount > 0 ? selection.getRangeAt(0).commonAncestorContainer : graph.cellEditor.textarea;

        function updateSize(elt, ignoreContains) {
          if (
            graph.cellEditor.textarea != null &&
            elt != graph.cellEditor.textarea &&
            graph.cellEditor.textarea.contains(elt) &&
            (ignoreContains || selection.containsNode(elt, true))
          ) {
            if (elt.nodeName == 'FONT') {
              elt.removeAttribute('size');
              elt.style.fontSize = fontSize + 'px';
            } else {
              var css = mxUtils.getCurrentStyle(elt);

              if (css.fontSize != fontSize + 'px') {
                if (mxUtils.getCurrentStyle(elt.parentNode).fontSize != fontSize + 'px') {
                  elt.style.fontSize = fontSize + 'px';
                } else {
                  elt.style.fontSize = '';
                }
              }
            }
          }
        }

        // Wraps text node or mixed selection with leading text in a font element
        if (container == graph.cellEditor.textarea || container.nodeType != mxConstants.NODETYPE_ELEMENT) {
          document.execCommand('fontSize', false, '1');
        }

        if (container != graph.cellEditor.textarea) {
          container = container.parentNode;
        }

        if (container != null && container.nodeType == mxConstants.NODETYPE_ELEMENT) {
          var elts = container.getElementsByTagName('*');
          updateSize(container);

          for (var i = 0; i < elts.length; i++) {
            updateSize(elts[i]);
          }
        }

        input.value = fontSize + ' pt';
      } else if (window.getSelection || document.selection) {
        // Checks selection
        var par = null;

        if (document.selection) {
          par = document.selection.createRange().parentElement();
        } else {
          var selection = window.getSelection();

          if (selection.rangeCount > 0) {
            par = selection.getRangeAt(0).commonAncestorContainer;
          }
        }

        // Node.contains does not work for text nodes in IE11
        function isOrContains(container, node) {
          while (node != null) {
            if (node === container) {
              return true;
            }

            node = node.parentNode;
          }

          return false;
        }

        if (par != null && isOrContains(graph.cellEditor.textarea, par)) {
          pendingFontSize = fontSize;

          // Workaround for can't set font size in px is to change font size afterwards
          document.execCommand('fontSize', false, '4');
          var elts = graph.cellEditor.textarea.getElementsByTagName('font');

          for (var i = 0; i < elts.length; i++) {
            if (elts[i].getAttribute('size') == '4') {
              elts[i].removeAttribute('size');
              elts[i].style.fontSize = pendingFontSize + 'px';

              // Overrides fontSize in input with the one just assigned as a workaround
              // for potential fontSize values of parent elements that don't match
              window.setTimeout(function () {
                input.value = pendingFontSize + ' pt';
                pendingFontSize = null;
              }, 0);

              break;
            }
          }
        }
      }
    },
    true
  );

  var stepper = this.createStepper(input, inputUpdate, 1, 10, true, Menus.prototype.defaultFontSize);
  stepper.style.display = input.style.display;
  stepper.style.marginTop = '4px';

  if (!mxClient.IS_QUIRKS) {
    stepper.style.right = '20px';
  }

  stylePanel2.appendChild(stepper);

  var arrow = fontMenu.getElementsByTagName('div')[0];
  arrow.style.cssFloat = 'right';

  var bgColorApply = null;
  var currentBgColor = '#ffffff';

  var fontColorApply = null;
  var currentFontColor = '#000000';

  var bgPanel = graph.cellEditor.isContentEditing()
    ? this.createColorOption(
        mxResources.get('backgroundColor'),
        function () {
          return currentBgColor;
        },
        function (color) {
          document.execCommand('backcolor', false, color != mxConstants.NONE ? color : 'transparent');
        },
        '#ffffff',
        {
          install: function (apply) {
            bgColorApply = apply;
          },
          destroy: function () {
            bgColorApply = null;
          },
        },
        null,
        true
      )
    : this.createCellColorOption(
        mxResources.get('backgroundColor'),
        mxConstants.STYLE_LABEL_BACKGROUNDCOLOR,
        '#ffffff',
        null,
        function (color) {
          graph.updateLabelElements(graph.getSelectionCells(), function (elt) {
            elt.style.backgroundColor = null;
          });
        }
      );
  bgPanel.style.fontWeight = 'bold';

  var borderPanel = this.createCellColorOption(
    mxResources.get('borderColor'),
    mxConstants.STYLE_LABEL_BORDERCOLOR,
    '#000000'
  );
  borderPanel.style.fontWeight = 'bold';

  var defs =
    ss.vertices.length >= 1 ? graph.stylesheet.getDefaultVertexStyle() : graph.stylesheet.getDefaultEdgeStyle();
  var panel = graph.cellEditor.isContentEditing()
    ? this.createColorOption(
        mxResources.get('fontColor'),
        function () {
          return currentFontColor;
        },
        function (color) {
          if (mxClient.IS_FF) {
            // Workaround for Firefox that adds the font element around
            // anchor elements which ignore inherited colors is to move
            // the font element inside anchor elements
            var tmp = graph.cellEditor.textarea.getElementsByTagName('font');
            var oldFonts = [];

            for (var i = 0; i < tmp.length; i++) {
              oldFonts.push({
                node: tmp[i],
                color: tmp[i].getAttribute('color'),
              });
            }

            document.execCommand('forecolor', false, color != mxConstants.NONE ? color : 'transparent');

            // Finds the new or changed font element
            var newFonts = graph.cellEditor.textarea.getElementsByTagName('font');

            for (var i = 0; i < newFonts.length; i++) {
              if (
                i >= oldFonts.length ||
                newFonts[i] != oldFonts[i].node ||
                (newFonts[i] == oldFonts[i].node && newFonts[i].getAttribute('color') != oldFonts[i].color)
              ) {
                var child = newFonts[i].firstChild;

                // Moves the font element to inside the anchor element and adopts all children
                if (child != null && child.nodeName == 'A' && child.nextSibling == null && child.firstChild != null) {
                  var parent = newFonts[i].parentNode;
                  parent.insertBefore(child, newFonts[i]);
                  var tmp = child.firstChild;

                  while (tmp != null) {
                    var next = tmp.nextSibling;
                    newFonts[i].appendChild(tmp);
                    tmp = next;
                  }

                  child.appendChild(newFonts[i]);
                }

                break;
              }
            }
          } else {
            document.execCommand('forecolor', false, color != mxConstants.NONE ? color : 'transparent');
          }
        },
        defs[mxConstants.STYLE_FONTCOLOR] != null ? defs[mxConstants.STYLE_FONTCOLOR] : '#000000',
        {
          install: function (apply) {
            fontColorApply = apply;
          },
          destroy: function () {
            fontColorApply = null;
          },
        },
        null,
        true
      )
    : this.createCellColorOption(
        mxResources.get('fontColor'),
        mxConstants.STYLE_FONTCOLOR,
        defs[mxConstants.STYLE_FONTCOLOR] != null ? defs[mxConstants.STYLE_FONTCOLOR] : '#000000',
        function (color) {
          if (color == mxConstants.NONE) {
            bgPanel.style.display = 'none';
          } else {
            bgPanel.style.display = '';
          }

          borderPanel.style.display = bgPanel.style.display;
        },
        function (color) {
          if (color == mxConstants.NONE) {
            graph.setCellStyles(mxConstants.STYLE_NOLABEL, '1', graph.getSelectionCells());
          } else {
            graph.setCellStyles(mxConstants.STYLE_NOLABEL, null, graph.getSelectionCells());
          }

          graph.updateCellStyles(mxConstants.STYLE_FONTCOLOR, color, graph.getSelectionCells());

          graph.updateLabelElements(graph.getSelectionCells(), function (elt) {
            elt.removeAttribute('color');
            elt.style.color = null;
          });
        }
      );
  panel.style.fontWeight = 'bold';

  colorPanel.appendChild(panel);
  colorPanel.appendChild(bgPanel);

  if (!graph.cellEditor.isContentEditing()) {
    colorPanel.appendChild(borderPanel);
  }

  container.appendChild(colorPanel);

  var extraPanel = this.createPanel();
  extraPanel.style.paddingTop = '2px';
  extraPanel.style.paddingBottom = '4px';

  // LATER: Fix toggle using '' instead of 'null'
  var wwOpt = this.createCellOption(
    mxResources.get('wordWrap'),
    mxConstants.STYLE_WHITE_SPACE,
    null,
    'wrap',
    'null',
    null,
    null,
    true
  );
  wwOpt.style.fontWeight = 'bold';

  // Word wrap in edge labels only supported via labelWidth style
  if (!ss.containsLabel && !ss.autoSize && ss.edges.length == 0) {
    extraPanel.appendChild(wwOpt);
  }

  // Delegates switch of style to formattedText action as it also convertes newlines
  var htmlOpt = this.createCellOption(
    mxResources.get('formattedText'),
    'html',
    '0',
    null,
    null,
    null,
    ui.actions.get('formattedText')
  );
  htmlOpt.style.fontWeight = 'bold';
  extraPanel.appendChild(htmlOpt);

  var spacingPanel = this.createPanel();
  spacingPanel.style.paddingTop = '10px';
  spacingPanel.style.paddingBottom = '28px';
  spacingPanel.style.fontWeight = 'normal';

  var span = document.createElement('div');
  span.style.position = 'absolute';
  span.style.width = '70px';
  span.style.marginTop = '0px';
  span.style.fontWeight = 'bold';
  mxUtils.write(span, mxResources.get('spacing'));
  spacingPanel.appendChild(span);

  var topUpdate, globalUpdate, leftUpdate, bottomUpdate, rightUpdate;
  var topSpacing = this.addUnitInput(spacingPanel, 'pt', 91, 44, function () {
    topUpdate.apply(this, arguments);
  });
  var globalSpacing = this.addUnitInput(spacingPanel, 'pt', 20, 44, function () {
    globalUpdate.apply(this, arguments);
  });

  mxUtils.br(spacingPanel);
  this.addLabel(spacingPanel, mxResources.get('top'), 91);
  this.addLabel(spacingPanel, mxResources.get('global'), 20);
  mxUtils.br(spacingPanel);
  mxUtils.br(spacingPanel);

  var leftSpacing = this.addUnitInput(spacingPanel, 'pt', 162, 44, function () {
    leftUpdate.apply(this, arguments);
  });
  var bottomSpacing = this.addUnitInput(spacingPanel, 'pt', 91, 44, function () {
    bottomUpdate.apply(this, arguments);
  });
  var rightSpacing = this.addUnitInput(spacingPanel, 'pt', 20, 44, function () {
    rightUpdate.apply(this, arguments);
  });

  mxUtils.br(spacingPanel);
  this.addLabel(spacingPanel, mxResources.get('left'), 162);
  this.addLabel(spacingPanel, mxResources.get('bottom'), 91);
  this.addLabel(spacingPanel, mxResources.get('right'), 20);

  if (!graph.cellEditor.isContentEditing()) {
    container.appendChild(extraPanel);
    container.appendChild(this.createRelativeOption(mxResources.get('opacity'), mxConstants.STYLE_TEXT_OPACITY));
    container.appendChild(spacingPanel);
  } else {
    var selState = null;
    var lineHeightInput = null;

    container.appendChild(
      this.createRelativeOption(
        mxResources.get('lineheight'),
        null,
        null,
        function (input) {
          var value = input.value == '' ? 120 : parseInt(input.value);
          value = Math.max(0, isNaN(value) ? 120 : value);

          if (selState != null) {
            graph.cellEditor.restoreSelection(selState);
            selState = null;
          }

          var selectedElement = graph.getSelectedElement();
          var node = selectedElement;

          while (node != null && node.nodeType != mxConstants.NODETYPE_ELEMENT) {
            node = node.parentNode;
          }

          if (node != null && node == graph.cellEditor.textarea && graph.cellEditor.textarea.firstChild != null) {
            if (graph.cellEditor.textarea.firstChild.nodeName != 'P') {
              graph.cellEditor.textarea.innerHTML = '<p>' + graph.cellEditor.textarea.innerHTML + '</p>';
            }

            node = graph.cellEditor.textarea.firstChild;
          }

          if (
            node != null &&
            graph.cellEditor.textarea != null &&
            node != graph.cellEditor.textarea &&
            graph.cellEditor.textarea.contains(node)
          ) {
            node.style.lineHeight = value + '%';
          }

          input.value = value + ' %';
        },
        function (input) {
          // Used in CSS handler to update current value
          lineHeightInput = input;

          // KNOWN: Arrow up/down clear selection text in quirks/IE 8
          // Text size via arrow button limits to 16 in IE11. Why?
          mxEvent.addListener(input, 'mousedown', function () {
            if (document.activeElement == graph.cellEditor.textarea) {
              selState = graph.cellEditor.saveSelection();
            }
          });

          mxEvent.addListener(input, 'touchstart', function () {
            if (document.activeElement == graph.cellEditor.textarea) {
              selState = graph.cellEditor.saveSelection();
            }
          });

          input.value = '120 %';
        }
      )
    );

    var insertPanel = stylePanel.cloneNode(false);
    insertPanel.style.paddingLeft = '0px';
    var insertBtns = this.editorUi.toolbar.addItems(['link', 'image'], insertPanel, true);

    var btns = [
      this.editorUi.toolbar.addButton(
        'geSprite-horizontalrule',
        mxResources.get('insertHorizontalRule'),
        function () {
          document.execCommand('inserthorizontalrule', false);
        },
        insertPanel
      ),
      this.editorUi.toolbar.addMenuFunctionInContainer(
        insertPanel,
        'geSprite-table',
        mxResources.get('table'),
        false,
        mxUtils.bind(this, function (menu) {
          this.editorUi.menus.addInsertTableItem(menu);
        })
      ),
    ];
    this.styleButtons(insertBtns);
    this.styleButtons(btns);

    var wrapper2 = this.createPanel();
    wrapper2.style.paddingTop = '10px';
    wrapper2.style.paddingBottom = '10px';
    wrapper2.appendChild(this.createTitle(mxResources.get('insert')));
    wrapper2.appendChild(insertPanel);
    container.appendChild(wrapper2);

    if (mxClient.IS_QUIRKS) {
      wrapper2.style.height = '70';
    }

    var tablePanel = stylePanel.cloneNode(false);
    tablePanel.style.paddingLeft = '0px';

    var btns = [
      this.editorUi.toolbar.addButton(
        'geSprite-insertcolumnbefore',
        mxResources.get('insertColumnBefore'),
        mxUtils.bind(this, function () {
          try {
            if (currentTable != null) {
              graph.insertColumn(currentTable, tableCell != null ? tableCell.cellIndex : 0);
            }
          } catch (e) {
            this.editorUi.handleError(e);
          }
        }),
        tablePanel
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-insertcolumnafter',
        mxResources.get('insertColumnAfter'),
        mxUtils.bind(this, function () {
          try {
            if (currentTable != null) {
              graph.insertColumn(currentTable, tableCell != null ? tableCell.cellIndex + 1 : -1);
            }
          } catch (e) {
            this.editorUi.handleError(e);
          }
        }),
        tablePanel
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-deletecolumn',
        mxResources.get('deleteColumn'),
        mxUtils.bind(this, function () {
          try {
            if (currentTable != null && tableCell != null) {
              graph.deleteColumn(currentTable, tableCell.cellIndex);
            }
          } catch (e) {
            this.editorUi.handleError(e);
          }
        }),
        tablePanel
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-insertrowbefore',
        mxResources.get('insertRowBefore'),
        mxUtils.bind(this, function () {
          try {
            if (currentTable != null && tableRow != null) {
              graph.insertRow(currentTable, tableRow.sectionRowIndex);
            }
          } catch (e) {
            this.editorUi.handleError(e);
          }
        }),
        tablePanel
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-insertrowafter',
        mxResources.get('insertRowAfter'),
        mxUtils.bind(this, function () {
          try {
            if (currentTable != null && tableRow != null) {
              graph.insertRow(currentTable, tableRow.sectionRowIndex + 1);
            }
          } catch (e) {
            this.editorUi.handleError(e);
          }
        }),
        tablePanel
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-deleterow',
        mxResources.get('deleteRow'),
        mxUtils.bind(this, function () {
          try {
            if (currentTable != null && tableRow != null) {
              graph.deleteRow(currentTable, tableRow.sectionRowIndex);
            }
          } catch (e) {
            this.editorUi.handleError(e);
          }
        }),
        tablePanel
      ),
    ];
    this.styleButtons(btns);
    btns[2].style.marginRight = '9px';

    var wrapper3 = this.createPanel();
    wrapper3.style.paddingTop = '10px';
    wrapper3.style.paddingBottom = '10px';
    wrapper3.appendChild(this.createTitle(mxResources.get('table')));
    wrapper3.appendChild(tablePanel);

    if (mxClient.IS_QUIRKS) {
      mxUtils.br(container);
      wrapper3.style.height = '70';
    }

    var tablePanel2 = stylePanel.cloneNode(false);
    tablePanel2.style.paddingLeft = '0px';

    var btns = [
      this.editorUi.toolbar.addButton(
        'geSprite-strokecolor',
        mxResources.get('borderColor'),
        mxUtils.bind(this, function (evt) {
          if (currentTable != null) {
            // Converts rgb(r,g,b) values
            var color = currentTable.style.borderColor.replace(
              /\brgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/g,
              function ($0, $1, $2, $3) {
                return (
                  '#' +
                  ('0' + Number($1).toString(16)).substr(-2) +
                  ('0' + Number($2).toString(16)).substr(-2) +
                  ('0' + Number($3).toString(16)).substr(-2)
                );
              }
            );
            this.editorUi.pickColor(color, function (newColor) {
              var targetElt =
                tableCell != null && (evt == null || !mxEvent.isShiftDown(evt)) ? tableCell : currentTable;

              graph.processElements(targetElt, function (elt) {
                elt.style.border = null;
              });

              if (newColor == null || newColor == mxConstants.NONE) {
                targetElt.removeAttribute('border');
                targetElt.style.border = '';
                targetElt.style.borderCollapse = '';
              } else {
                targetElt.setAttribute('border', '1');
                targetElt.style.border = '1px solid ' + newColor;
                targetElt.style.borderCollapse = 'collapse';
              }
            });
          }
        }),
        tablePanel2
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-fillcolor',
        mxResources.get('backgroundColor'),
        mxUtils.bind(this, function (evt) {
          // Converts rgb(r,g,b) values
          if (currentTable != null) {
            var color = currentTable.style.backgroundColor.replace(
              /\brgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/g,
              function ($0, $1, $2, $3) {
                return (
                  '#' +
                  ('0' + Number($1).toString(16)).substr(-2) +
                  ('0' + Number($2).toString(16)).substr(-2) +
                  ('0' + Number($3).toString(16)).substr(-2)
                );
              }
            );
            this.editorUi.pickColor(color, function (newColor) {
              var targetElt =
                tableCell != null && (evt == null || !mxEvent.isShiftDown(evt)) ? tableCell : currentTable;

              graph.processElements(targetElt, function (elt) {
                elt.style.backgroundColor = null;
              });

              if (newColor == null || newColor == mxConstants.NONE) {
                targetElt.style.backgroundColor = '';
              } else {
                targetElt.style.backgroundColor = newColor;
              }
            });
          }
        }),
        tablePanel2
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-fit',
        mxResources.get('spacing'),
        function () {
          if (currentTable != null) {
            var value = currentTable.getAttribute('cellPadding') || 0;

            var dlg = new FilenameDialog(
              ui,
              value,
              mxResources.get('apply'),
              mxUtils.bind(this, function (newValue) {
                if (newValue != null && newValue.length > 0) {
                  currentTable.setAttribute('cellPadding', newValue);
                } else {
                  currentTable.removeAttribute('cellPadding');
                }
              }),
              mxResources.get('spacing')
            );
            ui.showDialog(dlg.container, 300, 80, true, true);
            dlg.init();
          }
        },
        tablePanel2
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-left',
        mxResources.get('left'),
        function () {
          if (currentTable != null) {
            currentTable.setAttribute('align', 'left');
          }
        },
        tablePanel2
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-center',
        mxResources.get('center'),
        function () {
          if (currentTable != null) {
            currentTable.setAttribute('align', 'center');
          }
        },
        tablePanel2
      ),
      this.editorUi.toolbar.addButton(
        'geSprite-right',
        mxResources.get('right'),
        function () {
          if (currentTable != null) {
            currentTable.setAttribute('align', 'right');
          }
        },
        tablePanel2
      ),
    ];
    this.styleButtons(btns);
    btns[2].style.marginRight = '9px';

    if (mxClient.IS_QUIRKS) {
      mxUtils.br(wrapper3);
      mxUtils.br(wrapper3);
    }

    wrapper3.appendChild(tablePanel2);
    container.appendChild(wrapper3);

    tableWrapper = wrapper3;
  }

  function setSelected(elt, selected) {
    if (mxClient.IS_IE && (mxClient.IS_QUIRKS || document.documentMode < 10)) {
      elt.style.filter = selected
        ? 'progid:DXImageTransform.Microsoft.Gradient(' +
          "StartColorStr='#c5ecff', EndColorStr='#87d4fb', GradientType=0)"
        : '';
    } else {
      elt.style.backgroundImage = selected ? 'linear-gradient(#c5ecff 0px,#87d4fb 100%)' : '';
    }
  }

  var listener = mxUtils.bind(this, function (sender, evt, force) {
    ss = this.format.getSelectionState();
    var fontStyle = mxUtils.getValue(ss.style, mxConstants.STYLE_FONTSTYLE, 0);
    setSelected(fontStyleItems[0], (fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD);
    setSelected(fontStyleItems[1], (fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC);
    setSelected(fontStyleItems[2], (fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE);
    fontMenu.firstChild.nodeValue = mxUtils.getValue(
      ss.style,
      mxConstants.STYLE_FONTFAMILY,
      Menus.prototype.defaultFont
    );

    setSelected(verticalItem, mxUtils.getValue(ss.style, mxConstants.STYLE_HORIZONTAL, '1') == '0');

    if (force || document.activeElement != input) {
      var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_FONTSIZE, Menus.prototype.defaultFontSize));
      input.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    var align = mxUtils.getValue(ss.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
    setSelected(left, align == mxConstants.ALIGN_LEFT);
    setSelected(center, align == mxConstants.ALIGN_CENTER);
    setSelected(right, align == mxConstants.ALIGN_RIGHT);

    var valign = mxUtils.getValue(ss.style, mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
    setSelected(top, valign == mxConstants.ALIGN_TOP);
    setSelected(middle, valign == mxConstants.ALIGN_MIDDLE);
    setSelected(bottom, valign == mxConstants.ALIGN_BOTTOM);

    var pos = mxUtils.getValue(ss.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
    var vpos = mxUtils.getValue(ss.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);

    if (pos == mxConstants.ALIGN_LEFT && vpos == mxConstants.ALIGN_TOP) {
      positionSelect.value = 'topLeft';
    } else if (pos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_TOP) {
      positionSelect.value = 'top';
    } else if (pos == mxConstants.ALIGN_RIGHT && vpos == mxConstants.ALIGN_TOP) {
      positionSelect.value = 'topRight';
    } else if (pos == mxConstants.ALIGN_LEFT && vpos == mxConstants.ALIGN_BOTTOM) {
      positionSelect.value = 'bottomLeft';
    } else if (pos == mxConstants.ALIGN_CENTER && vpos == mxConstants.ALIGN_BOTTOM) {
      positionSelect.value = 'bottom';
    } else if (pos == mxConstants.ALIGN_RIGHT && vpos == mxConstants.ALIGN_BOTTOM) {
      positionSelect.value = 'bottomRight';
    } else if (pos == mxConstants.ALIGN_LEFT) {
      positionSelect.value = 'left';
    } else if (pos == mxConstants.ALIGN_RIGHT) {
      positionSelect.value = 'right';
    } else {
      positionSelect.value = 'center';
    }

    var dir = mxUtils.getValue(ss.style, mxConstants.STYLE_TEXT_DIRECTION, mxConstants.DEFAULT_TEXT_DIRECTION);

    if (dir == mxConstants.TEXT_DIRECTION_RTL) {
      dirSelect.value = 'rightToLeft';
    } else if (dir == mxConstants.TEXT_DIRECTION_LTR) {
      dirSelect.value = 'leftToRight';
    } else if (dir == mxConstants.TEXT_DIRECTION_AUTO) {
      dirSelect.value = 'automatic';
    }

    if (force || document.activeElement != globalSpacing) {
      var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_SPACING, 2));
      globalSpacing.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    if (force || document.activeElement != topSpacing) {
      var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_SPACING_TOP, 0));
      topSpacing.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    if (force || document.activeElement != rightSpacing) {
      var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_SPACING_RIGHT, 0));
      rightSpacing.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    if (force || document.activeElement != bottomSpacing) {
      var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_SPACING_BOTTOM, 0));
      bottomSpacing.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    if (force || document.activeElement != leftSpacing) {
      var tmp = parseFloat(mxUtils.getValue(ss.style, mxConstants.STYLE_SPACING_LEFT, 0));
      leftSpacing.value = isNaN(tmp) ? '' : tmp + ' pt';
    }
  });

  globalUpdate = this.installInputHandler(globalSpacing, mxConstants.STYLE_SPACING, 2, -999, 999, ' pt');
  topUpdate = this.installInputHandler(topSpacing, mxConstants.STYLE_SPACING_TOP, 0, -999, 999, ' pt');
  rightUpdate = this.installInputHandler(rightSpacing, mxConstants.STYLE_SPACING_RIGHT, 0, -999, 999, ' pt');
  bottomUpdate = this.installInputHandler(bottomSpacing, mxConstants.STYLE_SPACING_BOTTOM, 0, -999, 999, ' pt');
  leftUpdate = this.installInputHandler(leftSpacing, mxConstants.STYLE_SPACING_LEFT, 0, -999, 999, ' pt');

  this.addKeyHandler(input, listener);
  this.addKeyHandler(globalSpacing, listener);
  this.addKeyHandler(topSpacing, listener);
  this.addKeyHandler(rightSpacing, listener);
  this.addKeyHandler(bottomSpacing, listener);
  this.addKeyHandler(leftSpacing, listener);

  graph.getModel().addListener(mxEvent.CHANGE, listener);
  this.listeners.push({
    destroy: function () {
      graph.getModel().removeListener(listener);
    },
  });
  listener();

  if (graph.cellEditor.isContentEditing()) {
    var updating = false;

    var updateCssHandler = function () {
      if (!updating) {
        updating = true;

        window.setTimeout(function () {
          var node = graph.getSelectedEditingElement();

          if (node != null) {
            function getRelativeLineHeight(fontSize, css, elt) {
              if (elt.style != null && css != null) {
                var lineHeight = css.lineHeight;

                if (
                  elt.style.lineHeight != null &&
                  elt.style.lineHeight.substring(elt.style.lineHeight.length - 1) == '%'
                ) {
                  return parseInt(elt.style.lineHeight) / 100;
                } else {
                  return lineHeight.substring(lineHeight.length - 2) == 'px'
                    ? parseFloat(lineHeight) / fontSize
                    : parseInt(lineHeight);
                }
              } else {
                return '';
              }
            }

            function getAbsoluteFontSize(css) {
              var fontSize = css != null ? css.fontSize : null;

              if (fontSize != null && fontSize.substring(fontSize.length - 2) == 'px') {
                return parseFloat(fontSize);
              } else {
                return mxConstants.DEFAULT_FONTSIZE;
              }
            }

            var css = mxUtils.getCurrentStyle(node);
            var fontSize = getAbsoluteFontSize(css);
            var lineHeight = getRelativeLineHeight(fontSize, css, node);

            // Finds common font size
            var elts = node.getElementsByTagName('*');

            // IE does not support containsNode
            if (elts.length > 0 && window.getSelection && !mxClient.IS_IE && !mxClient.IS_IE11) {
              var selection = window.getSelection();

              for (var i = 0; i < elts.length; i++) {
                if (selection.containsNode(elts[i], true)) {
                  temp = mxUtils.getCurrentStyle(elts[i]);
                  fontSize = Math.max(getAbsoluteFontSize(temp), fontSize);
                  var lh = getRelativeLineHeight(fontSize, temp, elts[i]);

                  if (lh != lineHeight || isNaN(lh)) {
                    lineHeight = '';
                  }
                }
              }
            }

            function hasParentOrOnlyChild(name) {
              if (graph.getParentByName(node, name, graph.cellEditor.textarea) != null) {
                return true;
              } else {
                var child = node;

                while (child != null && child.childNodes.length == 1) {
                  child = child.childNodes[0];

                  if (child.nodeName == name) {
                    return true;
                  }
                }
              }

              return false;
            }

            function isEqualOrPrefixed(str, value) {
              if (str != null && value != null) {
                if (str == value) {
                  return true;
                } else if (str.length > value.length + 1) {
                  return str.substring(str.length - value.length - 1, str.length) == '-' + value;
                }
              }

              return false;
            }

            if (css != null) {
              setSelected(
                fontStyleItems[0],
                css.fontWeight == 'bold' ||
                  css.fontWeight > 400 ||
                  hasParentOrOnlyChild('B') ||
                  hasParentOrOnlyChild('STRONG')
              );
              setSelected(
                fontStyleItems[1],
                css.fontStyle == 'italic' || hasParentOrOnlyChild('I') || hasParentOrOnlyChild('EM')
              );
              setSelected(fontStyleItems[2], hasParentOrOnlyChild('U'));
              setSelected(sup, hasParentOrOnlyChild('SUP'));
              setSelected(sub, hasParentOrOnlyChild('SUB'));

              if (!graph.cellEditor.isTableSelected()) {
                var align =
                  graph.cellEditor.align ||
                  mxUtils.getValue(ss.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);

                if (isEqualOrPrefixed(css.textAlign, 'justify')) {
                  setSelected(full, isEqualOrPrefixed(css.textAlign, 'justify'));
                  setSelected(left, false);
                  setSelected(center, false);
                  setSelected(right, false);
                } else {
                  setSelected(full, false);
                  setSelected(left, align == mxConstants.ALIGN_LEFT);
                  setSelected(center, align == mxConstants.ALIGN_CENTER);
                  setSelected(right, align == mxConstants.ALIGN_RIGHT);
                }
              } else {
                setSelected(full, isEqualOrPrefixed(css.textAlign, 'justify'));
                setSelected(left, isEqualOrPrefixed(css.textAlign, 'left'));
                setSelected(center, isEqualOrPrefixed(css.textAlign, 'center'));
                setSelected(right, isEqualOrPrefixed(css.textAlign, 'right'));
              }

              currentTable = graph.getParentByName(node, 'TABLE', graph.cellEditor.textarea);
              tableRow = currentTable == null ? null : graph.getParentByName(node, 'TR', currentTable);
              tableCell = currentTable == null ? null : graph.getParentByNames(node, ['TD', 'TH'], currentTable);
              tableWrapper.style.display = currentTable != null ? '' : 'none';

              if (document.activeElement != input) {
                if (node.nodeName == 'FONT' && node.getAttribute('size') == '4' && pendingFontSize != null) {
                  node.removeAttribute('size');
                  node.style.fontSize = pendingFontSize + ' pt';
                  pendingFontSize = null;
                } else {
                  input.value = isNaN(fontSize) ? '' : fontSize + ' pt';
                }

                var lh = parseFloat(lineHeight);

                if (!isNaN(lh)) {
                  lineHeightInput.value = Math.round(lh * 100) + ' %';
                } else {
                  lineHeightInput.value = '100 %';
                }
              }

              // Converts rgb(r,g,b) values
              var color = css.color.replace(
                /\brgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/g,
                function ($0, $1, $2, $3) {
                  return (
                    '#' +
                    ('0' + Number($1).toString(16)).substr(-2) +
                    ('0' + Number($2).toString(16)).substr(-2) +
                    ('0' + Number($3).toString(16)).substr(-2)
                  );
                }
              );
              var color2 = css.backgroundColor.replace(
                /\brgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/g,
                function ($0, $1, $2, $3) {
                  return (
                    '#' +
                    ('0' + Number($1).toString(16)).substr(-2) +
                    ('0' + Number($2).toString(16)).substr(-2) +
                    ('0' + Number($3).toString(16)).substr(-2)
                  );
                }
              );

              // Updates the color picker for the current font
              if (fontColorApply != null) {
                if (color.charAt(0) == '#') {
                  currentFontColor = color;
                } else {
                  currentFontColor = '#000000';
                }

                fontColorApply(currentFontColor, true);
              }

              if (bgColorApply != null) {
                if (color2.charAt(0) == '#') {
                  currentBgColor = color2;
                } else {
                  currentBgColor = null;
                }

                bgColorApply(currentBgColor, true);
              }

              // Workaround for firstChild is null or not an object
              // in the log which seems to be IE8- only / 29.01.15
              if (fontMenu.firstChild != null) {
                fontMenu.firstChild.nodeValue = Graph.stripQuotes(css.fontFamily);
              }
            }
          }

          updating = false;
        }, 0);
      }
    };

    if (mxClient.IS_FF || mxClient.IS_EDGE || mxClient.IS_IE || mxClient.IS_IE11) {
      mxEvent.addListener(graph.cellEditor.textarea, 'DOMSubtreeModified', updateCssHandler);
    }

    mxEvent.addListener(graph.cellEditor.textarea, 'input', updateCssHandler);
    mxEvent.addListener(graph.cellEditor.textarea, 'touchend', updateCssHandler);
    mxEvent.addListener(graph.cellEditor.textarea, 'mouseup', updateCssHandler);
    mxEvent.addListener(graph.cellEditor.textarea, 'keyup', updateCssHandler);
    this.listeners.push({
      destroy: function () {
        // No need to remove listener since textarea is destroyed after edit
      },
    });
    updateCssHandler();
  }

  return container;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
StyleFormatPanel = function (format, editorUi, container) {
  BaseFormatPanel.call(this, format, editorUi, container);
  this.init();
};

mxUtils.extend(StyleFormatPanel, BaseFormatPanel);

/**
 *
 */
StyleFormatPanel.prototype.defaultStrokeColor = 'black';

/**
 * Adds the label menu items to the given menu and parent.
 */
StyleFormatPanel.prototype.init = function () {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;
  var ss = this.format.getSelectionState();

  if (!ss.containsLabel) {
    if (
      ss.containsImage &&
      ss.vertices.length == 1 &&
      ss.style.shape == 'image' &&
      ss.style.image != null &&
      ss.style.image.substring(0, 19) == 'data:image/svg+xml;'
    ) {
      this.container.appendChild(this.addSvgStyles(this.createPanel()));
    }

    if (!ss.containsImage || ss.style.shape == 'image') {
      this.container.appendChild(this.addFill(this.createPanel()));
    }

    this.container.appendChild(this.addStroke(this.createPanel()));
    this.container.appendChild(this.addLineJumps(this.createPanel()));
    var opacityPanel = this.createRelativeOption(mxResources.get('opacity'), mxConstants.STYLE_OPACITY, 41);
    opacityPanel.style.paddingTop = '8px';
    opacityPanel.style.paddingBottom = '8px';
    this.container.appendChild(opacityPanel);
    this.container.appendChild(this.addEffects(this.createPanel()));
  }

  var opsPanel = this.addEditOps(this.createPanel());

  if (opsPanel.firstChild != null) {
    mxUtils.br(opsPanel);
  }

  this.container.appendChild(this.addStyleOps(opsPanel));
};

/**
 * Use browser for parsing CSS.
 */
StyleFormatPanel.prototype.getCssRules = function (css) {
  var doc = document.implementation.createHTMLDocument('');
  var styleElement = document.createElement('style');

  mxUtils.setTextContent(styleElement, css);
  doc.body.appendChild(styleElement);

  return styleElement.sheet.cssRules;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
StyleFormatPanel.prototype.addSvgStyles = function (container) {
  var ui = this.editorUi;
  var graph = ui.editor.graph;
  var ss = this.format.getSelectionState();
  container.style.paddingTop = '6px';
  container.style.paddingBottom = '6px';
  container.style.fontWeight = 'bold';
  container.style.display = 'none';

  try {
    var exp = ss.style.editableCssRules;

    if (exp != null) {
      var regex = new RegExp(exp);

      var data = ss.style.image.substring(ss.style.image.indexOf(',') + 1);
      var xml = window.atob ? atob(data) : Base64.decode(data, true);
      var svg = mxUtils.parseXml(xml);

      if (svg != null) {
        var styles = svg.getElementsByTagName('style');

        for (var i = 0; i < styles.length; i++) {
          var rules = this.getCssRules(mxUtils.getTextContent(styles[i]));

          for (var j = 0; j < rules.length; j++) {
            this.addSvgRule(container, rules[j], svg, styles[i], rules, j, regex);
          }
        }
      }
    }
  } catch (e) {
    // ignore
  }

  return container;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
StyleFormatPanel.prototype.addSvgRule = function (container, rule, svg, styleElem, rules, ruleIndex, regex) {
  var ui = this.editorUi;
  var graph = ui.editor.graph;

  if (regex.test(rule.selectorText)) {
    function rgb2hex(rgb) {
      rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i);

      return rgb && rgb.length === 4
        ? '#' +
            ('0' + parseInt(rgb[1], 10).toString(16)).slice(-2) +
            ('0' + parseInt(rgb[2], 10).toString(16)).slice(-2) +
            ('0' + parseInt(rgb[3], 10).toString(16)).slice(-2)
        : '';
    }

    var addStyleRule = mxUtils.bind(this, function (rule, key, label) {
      var value = mxUtils.trim(rule.style[key]);

      if (value != '' && value.substring(0, 4) != 'url(') {
        var option = this.createColorOption(
          label + ' ' + rule.selectorText,
          function () {
            return rgb2hex(value);
          },
          function (color) {
            rules[ruleIndex].style[key] = color;
            var cssTxt = '';

            for (var i = 0; i < rules.length; i++) {
              cssTxt += rules[i].cssText + ' ';
            }

            styleElem.textContent = cssTxt;
            var xml = mxUtils.getXml(svg.documentElement);

            graph.setCellStyles(
              mxConstants.STYLE_IMAGE,
              'data:image/svg+xml,' + (window.btoa ? btoa(xml) : Base64.encode(xml, true)),
              graph.getSelectionCells()
            );
          },
          '#ffffff',
          {
            install: function (apply) {
              // ignore
            },
            destroy: function () {
              // ignore
            },
          }
        );

        container.appendChild(option);

        // Shows container if rules are added
        container.style.display = '';
      }
    });

    addStyleRule(rule, 'fill', mxResources.get('fill'));
    addStyleRule(rule, 'stroke', mxResources.get('line'));
    addStyleRule(rule, 'stop-color', mxResources.get('gradient'));
  }
};

/**
 * Adds the label menu items to the given menu and parent.
 */
StyleFormatPanel.prototype.addEditOps = function (div) {
  var ss = this.format.getSelectionState();
  var btn = null;

  if (this.editorUi.editor.graph.getSelectionCount() == 1) {
    btn = mxUtils.button(
      mxResources.get('editStyle'),
      mxUtils.bind(this, function (evt) {
        this.editorUi.actions.get('editStyle').funct();
      })
    );

    btn.setAttribute(
      'title',
      mxResources.get('editStyle') + ' (' + this.editorUi.actions.get('editStyle').shortcut + ')'
    );
    btn.style.width = '202px';
    btn.style.marginBottom = '2px';

    div.appendChild(btn);
  }

  if (ss.image) {
    var btn2 = mxUtils.button(
      mxResources.get('editImage'),
      mxUtils.bind(this, function (evt) {
        this.editorUi.actions.get('image').funct();
      })
    );

    btn2.setAttribute('title', mxResources.get('editImage'));
    btn2.style.marginBottom = '2px';

    if (btn == null) {
      btn2.style.width = '202px';
    } else {
      btn.style.width = '100px';
      btn2.style.width = '100px';
      btn2.style.marginLeft = '2px';
    }

    div.appendChild(btn2);
  }

  return div;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
StyleFormatPanel.prototype.addFill = function (container) {
  var ui = this.editorUi;
  var graph = ui.editor.graph;
  var ss = this.format.getSelectionState();
  container.style.paddingTop = '6px';
  container.style.paddingBottom = '6px';

  // Adds gradient direction option
  var gradientSelect = document.createElement('select');
  gradientSelect.style.position = 'absolute';
  gradientSelect.style.marginTop = '-2px';
  gradientSelect.style.right = mxClient.IS_QUIRKS ? '52px' : '72px';
  gradientSelect.style.width = '70px';

  var fillStyleSelect = gradientSelect.cloneNode(false);

  // Stops events from bubbling to color option event handler
  mxEvent.addListener(gradientSelect, 'click', function (evt) {
    mxEvent.consume(evt);
  });
  mxEvent.addListener(fillStyleSelect, 'click', function (evt) {
    mxEvent.consume(evt);
  });

  var defs =
    ss.vertices.length >= 1 ? graph.stylesheet.getDefaultVertexStyle() : graph.stylesheet.getDefaultEdgeStyle();
  var gradientPanel = this.createCellColorOption(
    mxResources.get('gradient'),
    mxConstants.STYLE_GRADIENTCOLOR,
    defs[mxConstants.STYLE_GRADIENTCOLOR] != null ? defs[mxConstants.STYLE_GRADIENTCOLOR] : '#ffffff',
    function (color) {
      if (color == null || color == mxConstants.NONE) {
        gradientSelect.style.display = 'none';
      } else {
        gradientSelect.style.display = '';
      }
    },
    function (color) {
      graph.updateCellStyles(mxConstants.STYLE_GRADIENTCOLOR, color, graph.getSelectionCells());
    }
  );

  var fillKey = ss.style.shape == 'image' ? mxConstants.STYLE_IMAGE_BACKGROUND : mxConstants.STYLE_FILLCOLOR;
  var label = ss.style.shape == 'image' ? mxResources.get('background') : mxResources.get('fill');

  var defs =
    ss.vertices.length >= 1 ? graph.stylesheet.getDefaultVertexStyle() : graph.stylesheet.getDefaultEdgeStyle();
  var fillPanel = this.createCellColorOption(
    label,
    fillKey,
    defs[fillKey] != null ? defs[fillKey] : '#ffffff',
    null,
    mxUtils.bind(this, function (color) {
      graph.updateCellStyles(fillKey, color, graph.getSelectionCells());
    })
  );
  fillPanel.style.fontWeight = 'bold';

  var tmpColor = mxUtils.getValue(ss.style, fillKey, null);
  gradientPanel.style.display =
    tmpColor != null && tmpColor != mxConstants.NONE && ss.fill && ss.style.shape != 'image' ? '' : 'none';

  var directions = [
    mxConstants.DIRECTION_NORTH,
    mxConstants.DIRECTION_EAST,
    mxConstants.DIRECTION_SOUTH,
    mxConstants.DIRECTION_WEST,
  ];

  for (var i = 0; i < directions.length; i++) {
    var gradientOption = document.createElement('option');
    gradientOption.setAttribute('value', directions[i]);
    mxUtils.write(gradientOption, mxResources.get(directions[i]));
    gradientSelect.appendChild(gradientOption);
  }

  gradientPanel.appendChild(gradientSelect);

  for (var i = 0; i < Editor.roughFillStyles.length; i++) {
    var fillStyleOption = document.createElement('option');
    fillStyleOption.setAttribute('value', Editor.roughFillStyles[i].val);
    mxUtils.write(fillStyleOption, Editor.roughFillStyles[i].dispName);
    fillStyleSelect.appendChild(fillStyleOption);
  }

  fillPanel.appendChild(fillStyleSelect);

  var listener = mxUtils.bind(this, function () {
    ss = this.format.getSelectionState();
    var value = mxUtils.getValue(ss.style, mxConstants.STYLE_GRADIENT_DIRECTION, mxConstants.DIRECTION_SOUTH);
    var fillStyle = mxUtils.getValue(ss.style, 'fillStyle', 'auto');

    // Handles empty string which is not allowed as a value
    if (value == '') {
      value = mxConstants.DIRECTION_SOUTH;
    }

    gradientSelect.value = value;
    fillStyleSelect.value = fillStyle;
    container.style.display = ss.fill ? '' : 'none';

    var fillColor = mxUtils.getValue(ss.style, mxConstants.STYLE_FILLCOLOR, null);

    if (
      !ss.fill ||
      ss.containsImage ||
      fillColor == null ||
      fillColor == mxConstants.NONE ||
      ss.style.shape == 'filledEdge'
    ) {
      fillStyleSelect.style.display = 'none';
      gradientPanel.style.display = 'none';
    } else {
      fillStyleSelect.style.display = ss.style.sketch == '1' ? '' : 'none';
      gradientPanel.style.display = ss.style.sketch != '1' || fillStyle == 'solid' || fillStyle == 'auto' ? '' : 'none';
    }
  });

  graph.getModel().addListener(mxEvent.CHANGE, listener);
  this.listeners.push({
    destroy: function () {
      graph.getModel().removeListener(listener);
    },
  });
  listener();

  mxEvent.addListener(gradientSelect, 'change', function (evt) {
    graph.setCellStyles(mxConstants.STYLE_GRADIENT_DIRECTION, gradientSelect.value, graph.getSelectionCells());
    mxEvent.consume(evt);
  });

  mxEvent.addListener(fillStyleSelect, 'change', function (evt) {
    graph.setCellStyles('fillStyle', fillStyleSelect.value, graph.getSelectionCells());
    mxEvent.consume(evt);
  });

  container.appendChild(fillPanel);
  container.appendChild(gradientPanel);

  // Adds custom colors
  var custom = this.getCustomColors();

  for (var i = 0; i < custom.length; i++) {
    container.appendChild(this.createCellColorOption(custom[i].title, custom[i].key, custom[i].defaultValue));
  }

  return container;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
StyleFormatPanel.prototype.getCustomColors = function () {
  var ss = this.format.getSelectionState();
  var result = [];

  if (ss.style.shape == 'swimlane' || ss.style.shape == 'table') {
    result.push({ title: mxResources.get('laneColor'), key: 'swimlaneFillColor', defaultValue: '#ffffff' });
  }

  return result;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
StyleFormatPanel.prototype.addStroke = function (container) {
  var ui = this.editorUi;
  var graph = ui.editor.graph;
  var ss = this.format.getSelectionState();

  container.style.paddingTop = '4px';
  container.style.paddingBottom = '4px';
  container.style.whiteSpace = 'normal';

  var colorPanel = document.createElement('div');
  colorPanel.style.fontWeight = 'bold';

  if (!ss.stroke) {
    colorPanel.style.display = 'none';
  }

  // Adds gradient direction option
  var styleSelect = document.createElement('select');
  styleSelect.style.position = 'absolute';
  styleSelect.style.marginTop = '-2px';
  styleSelect.style.right = '72px';
  styleSelect.style.width = '80px';

  var styles = ['sharp', 'rounded', 'curved'];

  for (var i = 0; i < styles.length; i++) {
    var styleOption = document.createElement('option');
    styleOption.setAttribute('value', styles[i]);
    mxUtils.write(styleOption, mxResources.get(styles[i]));
    styleSelect.appendChild(styleOption);
  }

  mxEvent.addListener(styleSelect, 'change', function (evt) {
    graph.getModel().beginUpdate();
    try {
      var keys = [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED];
      // Default for rounded is 1
      var values = ['0', null];

      if (styleSelect.value == 'rounded') {
        values = ['1', null];
      } else if (styleSelect.value == 'curved') {
        values = [null, '1'];
      }

      for (var i = 0; i < keys.length; i++) {
        graph.setCellStyles(keys[i], values[i], graph.getSelectionCells());
      }

      ui.fireEvent(
        new mxEventObject('styleChanged', 'keys', keys, 'values', values, 'cells', graph.getSelectionCells())
      );
    } finally {
      graph.getModel().endUpdate();
    }

    mxEvent.consume(evt);
  });

  // Stops events from bubbling to color option event handler
  mxEvent.addListener(styleSelect, 'click', function (evt) {
    mxEvent.consume(evt);
  });

  var strokeKey = ss.style.shape == 'image' ? mxConstants.STYLE_IMAGE_BORDER : mxConstants.STYLE_STROKECOLOR;
  var label = ss.style.shape == 'image' ? mxResources.get('border') : mxResources.get('line');

  var defs =
    ss.vertices.length >= 1 ? graph.stylesheet.getDefaultVertexStyle() : graph.stylesheet.getDefaultEdgeStyle();
  var lineColor = this.createCellColorOption(
    label,
    strokeKey,
    defs[strokeKey] != null ? defs[strokeKey] : '#000000',
    null,
    mxUtils.bind(this, function (color) {
      graph.updateCellStyles(strokeKey, color, graph.getSelectionCells());
    })
  );

  lineColor.appendChild(styleSelect);
  colorPanel.appendChild(lineColor);

  // Used if only edges selected
  var stylePanel = colorPanel.cloneNode(false);
  stylePanel.style.fontWeight = 'normal';
  stylePanel.style.whiteSpace = 'nowrap';
  stylePanel.style.position = 'relative';
  stylePanel.style.paddingLeft = '16px';
  stylePanel.style.marginBottom = '2px';
  stylePanel.style.marginTop = '2px';
  stylePanel.className = 'geToolbarContainer';

  var addItem = mxUtils.bind(this, function (menu, width, cssName, keys, values) {
    var item = this.editorUi.menus.styleChange(menu, '', keys, values, 'geIcon', null);

    var pat = document.createElement('div');
    pat.style.width = width + 'px';
    pat.style.height = '1px';
    pat.style.borderBottom = '1px ' + cssName + ' ' + this.defaultStrokeColor;
    pat.style.paddingTop = '6px';

    item.firstChild.firstChild.style.padding = '0px 4px 0px 4px';
    item.firstChild.firstChild.style.width = width + 'px';
    item.firstChild.firstChild.appendChild(pat);

    return item;
  });

  var pattern = this.editorUi.toolbar.addMenuFunctionInContainer(
    stylePanel,
    'geSprite-orthogonal',
    mxResources.get('pattern'),
    false,
    mxUtils.bind(this, function (menu) {
      addItem(menu, 75, 'solid', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], [null, null]).setAttribute(
        'title',
        mxResources.get('solid')
      );
      addItem(menu, 75, 'dashed', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', null]).setAttribute(
        'title',
        mxResources.get('dashed')
      );
      addItem(
        menu,
        75,
        'dotted',
        [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
        ['1', '1 1']
      ).setAttribute('title', mxResources.get('dotted') + ' (1)');
      addItem(
        menu,
        75,
        'dotted',
        [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
        ['1', '1 2']
      ).setAttribute('title', mxResources.get('dotted') + ' (2)');
      addItem(
        menu,
        75,
        'dotted',
        [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
        ['1', '1 4']
      ).setAttribute('title', mxResources.get('dotted') + ' (3)');
    })
  );

  // Used for mixed selection (vertices and edges)
  var altStylePanel = stylePanel.cloneNode(false);

  var edgeShape = this.editorUi.toolbar.addMenuFunctionInContainer(
    altStylePanel,
    'geSprite-connection',
    mxResources.get('connection'),
    false,
    mxUtils.bind(this, function (menu) {
      this.editorUi.menus
        .styleChange(
          menu,
          '',
          [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
          [null, null, null, null],
          'geIcon geSprite geSprite-connection',
          null,
          true
        )
        .setAttribute('title', mxResources.get('line'));
      this.editorUi.menus
        .styleChange(
          menu,
          '',
          [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
          ['link', null, null, null],
          'geIcon geSprite geSprite-linkedge',
          null,
          true
        )
        .setAttribute('title', mxResources.get('link'));
      this.editorUi.menus
        .styleChange(
          menu,
          '',
          [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
          ['flexArrow', null, null, null],
          'geIcon geSprite geSprite-arrow',
          null,
          true
        )
        .setAttribute('title', mxResources.get('arrow'));
      this.editorUi.menus
        .styleChange(
          menu,
          '',
          [mxConstants.STYLE_SHAPE, mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE, 'width'],
          ['arrow', null, null, null],
          'geIcon geSprite geSprite-simplearrow',
          null,
          true
        )
        .setAttribute('title', mxResources.get('simpleArrow'));
    })
  );

  var altPattern = this.editorUi.toolbar.addMenuFunctionInContainer(
    altStylePanel,
    'geSprite-orthogonal',
    mxResources.get('pattern'),
    false,
    mxUtils.bind(this, function (menu) {
      addItem(menu, 33, 'solid', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], [null, null]).setAttribute(
        'title',
        mxResources.get('solid')
      );
      addItem(menu, 33, 'dashed', [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN], ['1', null]).setAttribute(
        'title',
        mxResources.get('dashed')
      );
      addItem(
        menu,
        33,
        'dotted',
        [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
        ['1', '1 1']
      ).setAttribute('title', mxResources.get('dotted') + ' (1)');
      addItem(
        menu,
        33,
        'dotted',
        [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
        ['1', '1 2']
      ).setAttribute('title', mxResources.get('dotted') + ' (2)');
      addItem(
        menu,
        33,
        'dotted',
        [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
        ['1', '1 4']
      ).setAttribute('title', mxResources.get('dotted') + ' (3)');
    })
  );

  var stylePanel2 = stylePanel.cloneNode(false);

  // Stroke width
  var input = document.createElement('input');
  input.style.textAlign = 'right';
  input.style.marginTop = '2px';
  input.style.width = '41px';
  input.setAttribute('title', mxResources.get('linewidth'));

  stylePanel.appendChild(input);

  var altInput = input.cloneNode(true);
  altStylePanel.appendChild(altInput);

  function update(evt) {
    // Maximum stroke width is 999
    var value = parseInt(input.value);
    value = Math.min(999, Math.max(1, isNaN(value) ? 1 : value));

    if (value != mxUtils.getValue(ss.style, mxConstants.STYLE_STROKEWIDTH, 1)) {
      graph.setCellStyles(mxConstants.STYLE_STROKEWIDTH, value, graph.getSelectionCells());
      ui.fireEvent(
        new mxEventObject(
          'styleChanged',
          'keys',
          [mxConstants.STYLE_STROKEWIDTH],
          'values',
          [value],
          'cells',
          graph.getSelectionCells()
        )
      );
    }

    input.value = value + ' pt';
    mxEvent.consume(evt);
  }

  function altUpdate(evt) {
    // Maximum stroke width is 999
    var value = parseInt(altInput.value);
    value = Math.min(999, Math.max(1, isNaN(value) ? 1 : value));

    if (value != mxUtils.getValue(ss.style, mxConstants.STYLE_STROKEWIDTH, 1)) {
      graph.setCellStyles(mxConstants.STYLE_STROKEWIDTH, value, graph.getSelectionCells());
      ui.fireEvent(
        new mxEventObject(
          'styleChanged',
          'keys',
          [mxConstants.STYLE_STROKEWIDTH],
          'values',
          [value],
          'cells',
          graph.getSelectionCells()
        )
      );
    }

    altInput.value = value + ' pt';
    mxEvent.consume(evt);
  }

  var stepper = this.createStepper(input, update, 1, 9);
  stepper.style.display = input.style.display;
  stepper.style.marginTop = '2px';
  stylePanel.appendChild(stepper);

  var altStepper = this.createStepper(altInput, altUpdate, 1, 9);
  altStepper.style.display = altInput.style.display;
  altStepper.style.marginTop = '2px';
  altStylePanel.appendChild(altStepper);

  if (!mxClient.IS_QUIRKS) {
    input.style.position = 'absolute';
    input.style.height = '15px';
    input.style.left = '141px';
    stepper.style.left = '190px';

    altInput.style.position = 'absolute';
    altInput.style.left = '141px';
    altInput.style.height = '15px';
    altStepper.style.left = '190px';
  } else {
    input.style.height = '17px';
    altInput.style.height = '17px';
  }

  mxEvent.addListener(input, 'blur', update);
  mxEvent.addListener(input, 'change', update);

  mxEvent.addListener(altInput, 'blur', altUpdate);
  mxEvent.addListener(altInput, 'change', altUpdate);

  if (mxClient.IS_QUIRKS) {
    mxUtils.br(stylePanel2);
    mxUtils.br(stylePanel2);
  }

  var edgeStyle = this.editorUi.toolbar.addMenuFunctionInContainer(
    stylePanel2,
    'geSprite-orthogonal',
    mxResources.get('waypoints'),
    false,
    mxUtils.bind(this, function (menu) {
      if (ss.style.shape != 'arrow') {
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            [null, null, null],
            'geIcon geSprite geSprite-straight',
            null,
            true
          )
          .setAttribute('title', mxResources.get('straight'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['orthogonalEdgeStyle', null, null],
            'geIcon geSprite geSprite-orthogonal',
            null,
            true
          )
          .setAttribute('title', mxResources.get('orthogonal'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['elbowEdgeStyle', null, null, null],
            'geIcon geSprite geSprite-horizontalelbow',
            null,
            true
          )
          .setAttribute('title', mxResources.get('simple'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['elbowEdgeStyle', 'vertical', null, null],
            'geIcon geSprite geSprite-verticalelbow',
            null,
            true
          )
          .setAttribute('title', mxResources.get('simple'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['isometricEdgeStyle', null, null, null],
            'geIcon geSprite geSprite-horizontalisometric',
            null,
            true
          )
          .setAttribute('title', mxResources.get('isometric'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['isometricEdgeStyle', 'vertical', null, null],
            'geIcon geSprite geSprite-verticalisometric',
            null,
            true
          )
          .setAttribute('title', mxResources.get('isometric'));

        if (ss.style.shape == 'connector') {
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
              ['orthogonalEdgeStyle', '1', null],
              'geIcon geSprite geSprite-curved',
              null,
              true
            )
            .setAttribute('title', mxResources.get('curved'));
        }

        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['entityRelationEdgeStyle', null, null],
            'geIcon geSprite geSprite-entity',
            null,
            true
          )
          .setAttribute('title', mxResources.get('entityRelation'));
      }
    })
  );

  var lineStart = this.editorUi.toolbar.addMenuFunctionInContainer(
    stylePanel2,
    'geSprite-startclassic',
    mxResources.get('linestart'),
    false,
    mxUtils.bind(this, function (menu) {
      if (ss.style.shape == 'connector' || ss.style.shape == 'flexArrow' || ss.style.shape == 'filledEdge') {
        var item = this.editorUi.menus.edgeStyleChange(
          menu,
          '',
          [mxConstants.STYLE_STARTARROW, 'startFill'],
          [mxConstants.NONE, 0],
          'geIcon',
          null,
          false
        );
        item.setAttribute('title', mxResources.get('none'));
        item.firstChild.firstChild.innerHTML =
          '<font style="font-size:10px;">' + mxUtils.htmlEntities(mxResources.get('none')) + '</font>';

        if (ss.style.shape == 'connector' || ss.style.shape == 'filledEdge') {
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_CLASSIC, 1],
              'geIcon geSprite geSprite-startclassic',
              null,
              false
            )
            .setAttribute('title', mxResources.get('classic'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            [mxConstants.ARROW_CLASSIC_THIN, 1],
            'geIcon geSprite geSprite-startclassicthin',
            null,
            false
          );
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_OPEN, 0],
              'geIcon geSprite geSprite-startopen',
              null,
              false
            )
            .setAttribute('title', mxResources.get('openArrow'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            [mxConstants.ARROW_OPEN_THIN, 0],
            'geIcon geSprite geSprite-startopenthin',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['openAsync', 0],
            'geIcon geSprite geSprite-startopenasync',
            null,
            false
          );
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_BLOCK, 1],
              'geIcon geSprite geSprite-startblock',
              null,
              false
            )
            .setAttribute('title', mxResources.get('block'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            [mxConstants.ARROW_BLOCK_THIN, 1],
            'geIcon geSprite geSprite-startblockthin',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['async', 1],
            'geIcon geSprite geSprite-startasync',
            null,
            false
          );
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_OVAL, 1],
              'geIcon geSprite geSprite-startoval',
              null,
              false
            )
            .setAttribute('title', mxResources.get('oval'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_DIAMOND, 1],
              'geIcon geSprite geSprite-startdiamond',
              null,
              false
            )
            .setAttribute('title', mxResources.get('diamond'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_DIAMOND_THIN, 1],
              'geIcon geSprite geSprite-startthindiamond',
              null,
              false
            )
            .setAttribute('title', mxResources.get('diamondThin'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_CLASSIC, 0],
              'geIcon geSprite geSprite-startclassictrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('classic'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            [mxConstants.ARROW_CLASSIC_THIN, 0],
            'geIcon geSprite geSprite-startclassicthintrans',
            null,
            false
          );
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_BLOCK, 0],
              'geIcon geSprite geSprite-startblocktrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('block'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            [mxConstants.ARROW_BLOCK_THIN, 0],
            'geIcon geSprite geSprite-startblockthintrans',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['async', 0],
            'geIcon geSprite geSprite-startasynctrans',
            null,
            false
          );
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_OVAL, 0],
              'geIcon geSprite geSprite-startovaltrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('oval'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_DIAMOND, 0],
              'geIcon geSprite geSprite-startdiamondtrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('diamond'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW, 'startFill'],
              [mxConstants.ARROW_DIAMOND_THIN, 0],
              'geIcon geSprite geSprite-startthindiamondtrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('diamondThin'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['box', 0],
            'geIcon geSprite geSvgSprite geSprite-box',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['halfCircle', 0],
            'geIcon geSprite geSvgSprite geSprite-halfCircle',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['dash', 0],
            'geIcon geSprite geSprite-startdash',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['cross', 0],
            'geIcon geSprite geSprite-startcross',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['circlePlus', 0],
            'geIcon geSprite geSprite-startcircleplus',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['circle', 1],
            'geIcon geSprite geSprite-startcircle',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['ERone', 0],
            'geIcon geSprite geSprite-starterone',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['ERmandOne', 0],
            'geIcon geSprite geSprite-starteronetoone',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['ERmany', 0],
            'geIcon geSprite geSprite-startermany',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['ERoneToMany', 0],
            'geIcon geSprite geSprite-starteronetomany',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['ERzeroToOne', 1],
            'geIcon geSprite geSprite-starteroneopt',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_STARTARROW, 'startFill'],
            ['ERzeroToMany', 1],
            'geIcon geSprite geSprite-startermanyopt',
            null,
            false
          );
        } else {
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_STARTARROW],
              [mxConstants.ARROW_BLOCK],
              'geIcon geSprite geSprite-startblocktrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('block'));
        }
      }
    })
  );

  var lineEnd = this.editorUi.toolbar.addMenuFunctionInContainer(
    stylePanel2,
    'geSprite-endclassic',
    mxResources.get('lineend'),
    false,
    mxUtils.bind(this, function (menu) {
      if (ss.style.shape == 'connector' || ss.style.shape == 'flexArrow' || ss.style.shape == 'filledEdge') {
        var item = this.editorUi.menus.edgeStyleChange(
          menu,
          '',
          [mxConstants.STYLE_ENDARROW, 'endFill'],
          [mxConstants.NONE, 0],
          'geIcon',
          null,
          false
        );
        item.setAttribute('title', mxResources.get('none'));
        item.firstChild.firstChild.innerHTML =
          '<font style="font-size:10px;">' + mxUtils.htmlEntities(mxResources.get('none')) + '</font>';

        if (ss.style.shape == 'connector' || ss.style.shape == 'filledEdge') {
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_CLASSIC, 1],
              'geIcon geSprite geSprite-endclassic',
              null,
              false
            )
            .setAttribute('title', mxResources.get('classic'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            [mxConstants.ARROW_CLASSIC_THIN, 1],
            'geIcon geSprite geSprite-endclassicthin',
            null,
            false
          );
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_OPEN, 0],
              'geIcon geSprite geSprite-endopen',
              null,
              false
            )
            .setAttribute('title', mxResources.get('openArrow'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            [mxConstants.ARROW_OPEN_THIN, 0],
            'geIcon geSprite geSprite-endopenthin',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['openAsync', 0],
            'geIcon geSprite geSprite-endopenasync',
            null,
            false
          );
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_BLOCK, 1],
              'geIcon geSprite geSprite-endblock',
              null,
              false
            )
            .setAttribute('title', mxResources.get('block'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            [mxConstants.ARROW_BLOCK_THIN, 1],
            'geIcon geSprite geSprite-endblockthin',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['async', 1],
            'geIcon geSprite geSprite-endasync',
            null,
            false
          );
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_OVAL, 1],
              'geIcon geSprite geSprite-endoval',
              null,
              false
            )
            .setAttribute('title', mxResources.get('oval'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_DIAMOND, 1],
              'geIcon geSprite geSprite-enddiamond',
              null,
              false
            )
            .setAttribute('title', mxResources.get('diamond'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_DIAMOND_THIN, 1],
              'geIcon geSprite geSprite-endthindiamond',
              null,
              false
            )
            .setAttribute('title', mxResources.get('diamondThin'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_CLASSIC, 0],
              'geIcon geSprite geSprite-endclassictrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('classic'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            [mxConstants.ARROW_CLASSIC_THIN, 0],
            'geIcon geSprite geSprite-endclassicthintrans',
            null,
            false
          );
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_BLOCK, 0],
              'geIcon geSprite geSprite-endblocktrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('block'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            [mxConstants.ARROW_BLOCK_THIN, 0],
            'geIcon geSprite geSprite-endblockthintrans',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['async', 0],
            'geIcon geSprite geSprite-endasynctrans',
            null,
            false
          );
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_OVAL, 0],
              'geIcon geSprite geSprite-endovaltrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('oval'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_DIAMOND, 0],
              'geIcon geSprite geSprite-enddiamondtrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('diamond'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW, 'endFill'],
              [mxConstants.ARROW_DIAMOND_THIN, 0],
              'geIcon geSprite geSprite-endthindiamondtrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('diamondThin'));
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['box', 0],
            'geIcon geSprite geSvgSprite geFlipSprite geSprite-box',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['halfCircle', 0],
            'geIcon geSprite geSvgSprite geFlipSprite geSprite-halfCircle',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['dash', 0],
            'geIcon geSprite geSprite-enddash',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['cross', 0],
            'geIcon geSprite geSprite-endcross',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['circlePlus', 0],
            'geIcon geSprite geSprite-endcircleplus',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['circle', 1],
            'geIcon geSprite geSprite-endcircle',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['ERone', 0],
            'geIcon geSprite geSprite-enderone',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['ERmandOne', 0],
            'geIcon geSprite geSprite-enderonetoone',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['ERmany', 0],
            'geIcon geSprite geSprite-endermany',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['ERoneToMany', 0],
            'geIcon geSprite geSprite-enderonetomany',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['ERzeroToOne', 1],
            'geIcon geSprite geSprite-enderoneopt',
            null,
            false
          );
          this.editorUi.menus.edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_ENDARROW, 'endFill'],
            ['ERzeroToMany', 1],
            'geIcon geSprite geSprite-endermanyopt',
            null,
            false
          );
        } else {
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_ENDARROW],
              [mxConstants.ARROW_BLOCK],
              'geIcon geSprite geSprite-endblocktrans',
              null,
              false
            )
            .setAttribute('title', mxResources.get('block'));
        }
      }
    })
  );

  this.addArrow(edgeShape, 8);
  this.addArrow(edgeStyle);
  this.addArrow(lineStart);
  this.addArrow(lineEnd);

  var symbol = this.addArrow(pattern, 9);
  symbol.className = 'geIcon';
  symbol.style.width = 'auto';

  var altSymbol = this.addArrow(altPattern, 9);
  altSymbol.className = 'geIcon';
  altSymbol.style.width = '22px';

  var solid = document.createElement('div');
  solid.style.width = '85px';
  solid.style.height = '1px';
  solid.style.borderBottom = '1px solid ' + this.defaultStrokeColor;
  solid.style.marginBottom = '9px';
  symbol.appendChild(solid);

  var altSolid = document.createElement('div');
  altSolid.style.width = '23px';
  altSolid.style.height = '1px';
  altSolid.style.borderBottom = '1px solid ' + this.defaultStrokeColor;
  altSolid.style.marginBottom = '9px';
  altSymbol.appendChild(altSolid);

  pattern.style.height = '15px';
  altPattern.style.height = '15px';
  edgeShape.style.height = '15px';
  edgeStyle.style.height = '17px';
  lineStart.style.marginLeft = '3px';
  lineStart.style.height = '17px';
  lineEnd.style.marginLeft = '3px';
  lineEnd.style.height = '17px';

  container.appendChild(colorPanel);
  container.appendChild(altStylePanel);
  container.appendChild(stylePanel);

  var arrowPanel = stylePanel.cloneNode(false);
  arrowPanel.style.paddingBottom = '6px';
  arrowPanel.style.paddingTop = '4px';
  arrowPanel.style.fontWeight = 'normal';

  var span = document.createElement('div');
  span.style.position = 'absolute';
  span.style.marginLeft = '3px';
  span.style.marginBottom = '12px';
  span.style.marginTop = '2px';
  span.style.fontWeight = 'normal';
  span.style.width = '76px';

  mxUtils.write(span, mxResources.get('lineend'));
  arrowPanel.appendChild(span);

  var endSpacingUpdate, endSizeUpdate;
  var endSpacing = this.addUnitInput(arrowPanel, 'pt', 74, 33, function () {
    endSpacingUpdate.apply(this, arguments);
  });
  var endSize = this.addUnitInput(arrowPanel, 'pt', 20, 33, function () {
    endSizeUpdate.apply(this, arguments);
  });

  mxUtils.br(arrowPanel);

  var spacer = document.createElement('div');
  spacer.style.height = '8px';
  arrowPanel.appendChild(spacer);

  span = span.cloneNode(false);
  mxUtils.write(span, mxResources.get('linestart'));
  arrowPanel.appendChild(span);

  var startSpacingUpdate, startSizeUpdate;
  var startSpacing = this.addUnitInput(arrowPanel, 'pt', 74, 33, function () {
    startSpacingUpdate.apply(this, arguments);
  });
  var startSize = this.addUnitInput(arrowPanel, 'pt', 20, 33, function () {
    startSizeUpdate.apply(this, arguments);
  });

  mxUtils.br(arrowPanel);
  this.addLabel(arrowPanel, mxResources.get('spacing'), 74, 50);
  this.addLabel(arrowPanel, mxResources.get('size'), 20, 50);
  mxUtils.br(arrowPanel);

  var perimeterPanel = colorPanel.cloneNode(false);
  perimeterPanel.style.fontWeight = 'normal';
  perimeterPanel.style.position = 'relative';
  perimeterPanel.style.paddingLeft = '16px';
  perimeterPanel.style.marginBottom = '2px';
  perimeterPanel.style.marginTop = '6px';
  perimeterPanel.style.borderWidth = '0px';
  perimeterPanel.style.paddingBottom = '18px';

  var span = document.createElement('div');
  span.style.position = 'absolute';
  span.style.marginLeft = '3px';
  span.style.marginBottom = '12px';
  span.style.marginTop = '1px';
  span.style.fontWeight = 'normal';
  span.style.width = '120px';
  mxUtils.write(span, mxResources.get('perimeter'));
  perimeterPanel.appendChild(span);

  var perimeterUpdate;
  var perimeterSpacing = this.addUnitInput(perimeterPanel, 'pt', 20, 41, function () {
    perimeterUpdate.apply(this, arguments);
  });

  if (ss.edges.length == graph.getSelectionCount()) {
    container.appendChild(stylePanel2);

    if (mxClient.IS_QUIRKS) {
      mxUtils.br(container);
      mxUtils.br(container);
    }

    container.appendChild(arrowPanel);
  } else if (ss.vertices.length == graph.getSelectionCount()) {
    if (mxClient.IS_QUIRKS) {
      mxUtils.br(container);
    }

    container.appendChild(perimeterPanel);
  }

  var listener = mxUtils.bind(this, function (sender, evt, force) {
    ss = this.format.getSelectionState();
    var color = mxUtils.getValue(ss.style, strokeKey, null);

    if (force || document.activeElement != input) {
      var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_STROKEWIDTH, 1));
      input.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    if (force || document.activeElement != altInput) {
      var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_STROKEWIDTH, 1));
      altInput.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    styleSelect.style.visibility = ss.style.shape == 'connector' || ss.style.shape == 'filledEdge' ? '' : 'hidden';

    if (mxUtils.getValue(ss.style, mxConstants.STYLE_CURVED, null) == '1') {
      styleSelect.value = 'curved';
    } else if (mxUtils.getValue(ss.style, mxConstants.STYLE_ROUNDED, null) == '1') {
      styleSelect.value = 'rounded';
    }

    if (mxUtils.getValue(ss.style, mxConstants.STYLE_DASHED, null) == '1') {
      if (mxUtils.getValue(ss.style, mxConstants.STYLE_DASH_PATTERN, null) == null) {
        solid.style.borderBottom = '1px dashed ' + this.defaultStrokeColor;
      } else {
        solid.style.borderBottom = '1px dotted ' + this.defaultStrokeColor;
      }
    } else {
      solid.style.borderBottom = '1px solid ' + this.defaultStrokeColor;
    }

    altSolid.style.borderBottom = solid.style.borderBottom;

    // Updates toolbar icon for edge style
    var edgeStyleDiv = edgeStyle.getElementsByTagName('div')[0];

    if (edgeStyleDiv != null) {
      var es = mxUtils.getValue(ss.style, mxConstants.STYLE_EDGE, null);

      if (mxUtils.getValue(ss.style, mxConstants.STYLE_NOEDGESTYLE, null) == '1') {
        es = null;
      }

      if (es == 'orthogonalEdgeStyle' && mxUtils.getValue(ss.style, mxConstants.STYLE_CURVED, null) == '1') {
        edgeStyleDiv.className = 'geSprite geSprite-curved';
      } else if (es == 'straight' || es == 'none' || es == null) {
        edgeStyleDiv.className = 'geSprite geSprite-straight';
      } else if (es == 'entityRelationEdgeStyle') {
        edgeStyleDiv.className = 'geSprite geSprite-entity';
      } else if (es == 'elbowEdgeStyle') {
        edgeStyleDiv.className =
          'geSprite ' +
          (mxUtils.getValue(ss.style, mxConstants.STYLE_ELBOW, null) == 'vertical'
            ? 'geSprite-verticalelbow'
            : 'geSprite-horizontalelbow');
      } else if (es == 'isometricEdgeStyle') {
        edgeStyleDiv.className =
          'geSprite ' +
          (mxUtils.getValue(ss.style, mxConstants.STYLE_ELBOW, null) == 'vertical'
            ? 'geSprite-verticalisometric'
            : 'geSprite-horizontalisometric');
      } else {
        edgeStyleDiv.className = 'geSprite geSprite-orthogonal';
      }
    }

    // Updates icon for edge shape
    var edgeShapeDiv = edgeShape.getElementsByTagName('div')[0];

    if (edgeShapeDiv != null) {
      if (ss.style.shape == 'link') {
        edgeShapeDiv.className = 'geSprite geSprite-linkedge';
      } else if (ss.style.shape == 'flexArrow') {
        edgeShapeDiv.className = 'geSprite geSprite-arrow';
      } else if (ss.style.shape == 'arrow') {
        edgeShapeDiv.className = 'geSprite geSprite-simplearrow';
      } else {
        edgeShapeDiv.className = 'geSprite geSprite-connection';
      }
    }

    if (ss.edges.length == graph.getSelectionCount()) {
      altStylePanel.style.display = '';
      stylePanel.style.display = 'none';
    } else {
      altStylePanel.style.display = 'none';
      stylePanel.style.display = '';
    }

    function updateArrow(marker, fill, elt, prefix) {
      var markerDiv = elt.getElementsByTagName('div')[0];

      if (markerDiv != null) {
        markerDiv.className = ui.getCssClassForMarker(prefix, ss.style.shape, marker, fill);

        if (markerDiv.className == 'geSprite geSprite-noarrow') {
          markerDiv.innerHTML = mxUtils.htmlEntities(mxResources.get('none'));
          markerDiv.style.backgroundImage = 'none';
          markerDiv.style.verticalAlign = 'top';
          markerDiv.style.marginTop = '5px';
          markerDiv.style.fontSize = '10px';
          markerDiv.style.filter = 'none';
          markerDiv.style.color = this.defaultStrokeColor;
          markerDiv.nextSibling.style.marginTop = '0px';
        }
      }

      return markerDiv;
    }

    var sourceDiv = updateArrow(
      mxUtils.getValue(ss.style, mxConstants.STYLE_STARTARROW, null),
      mxUtils.getValue(ss.style, 'startFill', '1'),
      lineStart,
      'start'
    );
    var targetDiv = updateArrow(
      mxUtils.getValue(ss.style, mxConstants.STYLE_ENDARROW, null),
      mxUtils.getValue(ss.style, 'endFill', '1'),
      lineEnd,
      'end'
    );

    // Special cases for markers
    if (sourceDiv != null && targetDiv != null) {
      if (ss.style.shape == 'arrow') {
        sourceDiv.className = 'geSprite geSprite-noarrow';
        targetDiv.className = 'geSprite geSprite-endblocktrans';
      } else if (ss.style.shape == 'link') {
        sourceDiv.className = 'geSprite geSprite-noarrow';
        targetDiv.className = 'geSprite geSprite-noarrow';
      }
    }

    mxUtils.setOpacity(edgeStyle, ss.style.shape == 'arrow' ? 30 : 100);

    if (ss.style.shape != 'connector' && ss.style.shape != 'flexArrow' && ss.style.shape != 'filledEdge') {
      mxUtils.setOpacity(lineStart, 30);
      mxUtils.setOpacity(lineEnd, 30);
    } else {
      mxUtils.setOpacity(lineStart, 100);
      mxUtils.setOpacity(lineEnd, 100);
    }

    if (force || document.activeElement != startSize) {
      var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE));
      startSize.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    if (force || document.activeElement != startSpacing) {
      var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_SOURCE_PERIMETER_SPACING, 0));
      startSpacing.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    if (force || document.activeElement != endSize) {
      var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE));
      endSize.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    if (force || document.activeElement != startSpacing) {
      var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_TARGET_PERIMETER_SPACING, 0));
      endSpacing.value = isNaN(tmp) ? '' : tmp + ' pt';
    }

    if (force || document.activeElement != perimeterSpacing) {
      var tmp = parseInt(mxUtils.getValue(ss.style, mxConstants.STYLE_PERIMETER_SPACING, 0));
      perimeterSpacing.value = isNaN(tmp) ? '' : tmp + ' pt';
    }
  });

  startSizeUpdate = this.installInputHandler(
    startSize,
    mxConstants.STYLE_STARTSIZE,
    mxConstants.DEFAULT_MARKERSIZE,
    0,
    999,
    ' pt'
  );
  startSpacingUpdate = this.installInputHandler(
    startSpacing,
    mxConstants.STYLE_SOURCE_PERIMETER_SPACING,
    0,
    -999,
    999,
    ' pt'
  );
  endSizeUpdate = this.installInputHandler(
    endSize,
    mxConstants.STYLE_ENDSIZE,
    mxConstants.DEFAULT_MARKERSIZE,
    0,
    999,
    ' pt'
  );
  endSpacingUpdate = this.installInputHandler(
    endSpacing,
    mxConstants.STYLE_TARGET_PERIMETER_SPACING,
    0,
    -999,
    999,
    ' pt'
  );
  perimeterUpdate = this.installInputHandler(perimeterSpacing, mxConstants.STYLE_PERIMETER_SPACING, 0, 0, 999, ' pt');

  this.addKeyHandler(input, listener);
  this.addKeyHandler(startSize, listener);
  this.addKeyHandler(startSpacing, listener);
  this.addKeyHandler(endSize, listener);
  this.addKeyHandler(endSpacing, listener);
  this.addKeyHandler(perimeterSpacing, listener);

  graph.getModel().addListener(mxEvent.CHANGE, listener);
  this.listeners.push({
    destroy: function () {
      graph.getModel().removeListener(listener);
    },
  });
  listener();

  return container;
};

/**
 * Adds UI for configuring line jumps.
 */
StyleFormatPanel.prototype.addLineJumps = function (container) {
  var ss = this.format.getSelectionState();

  if (Graph.lineJumpsEnabled && ss.edges.length > 0 && ss.vertices.length == 0 && ss.lineJumps) {
    container.style.padding = '8px 0px 24px 18px';

    var ui = this.editorUi;
    var editor = ui.editor;
    var graph = editor.graph;

    var span = document.createElement('div');
    span.style.position = 'absolute';
    span.style.fontWeight = 'bold';
    span.style.width = '80px';

    mxUtils.write(span, mxResources.get('lineJumps'));
    container.appendChild(span);

    var styleSelect = document.createElement('select');
    styleSelect.style.position = 'absolute';
    styleSelect.style.marginTop = '-2px';
    styleSelect.style.right = '76px';
    styleSelect.style.width = '62px';

    var styles = ['none', 'arc', 'gap', 'sharp'];

    for (var i = 0; i < styles.length; i++) {
      var styleOption = document.createElement('option');
      styleOption.setAttribute('value', styles[i]);
      mxUtils.write(styleOption, mxResources.get(styles[i]));
      styleSelect.appendChild(styleOption);
    }

    mxEvent.addListener(styleSelect, 'change', function (evt) {
      graph.getModel().beginUpdate();
      try {
        graph.setCellStyles('jumpStyle', styleSelect.value, graph.getSelectionCells());
        ui.fireEvent(
          new mxEventObject(
            'styleChanged',
            'keys',
            ['jumpStyle'],
            'values',
            [styleSelect.value],
            'cells',
            graph.getSelectionCells()
          )
        );
      } finally {
        graph.getModel().endUpdate();
      }

      mxEvent.consume(evt);
    });

    // Stops events from bubbling to color option event handler
    mxEvent.addListener(styleSelect, 'click', function (evt) {
      mxEvent.consume(evt);
    });

    container.appendChild(styleSelect);

    var jumpSizeUpdate;

    var jumpSize = this.addUnitInput(container, 'pt', 22, 33, function () {
      jumpSizeUpdate.apply(this, arguments);
    });

    jumpSizeUpdate = this.installInputHandler(jumpSize, 'jumpSize', Graph.defaultJumpSize, 0, 999, ' pt');

    var listener = mxUtils.bind(this, function (sender, evt, force) {
      ss = this.format.getSelectionState();
      styleSelect.value = mxUtils.getValue(ss.style, 'jumpStyle', 'none');

      if (force || document.activeElement != jumpSize) {
        var tmp = parseInt(mxUtils.getValue(ss.style, 'jumpSize', Graph.defaultJumpSize));
        jumpSize.value = isNaN(tmp) ? '' : tmp + ' pt';
      }
    });

    this.addKeyHandler(jumpSize, listener);

    graph.getModel().addListener(mxEvent.CHANGE, listener);
    this.listeners.push({
      destroy: function () {
        graph.getModel().removeListener(listener);
      },
    });
    listener();
  } else {
    container.style.display = 'none';
  }

  return container;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
StyleFormatPanel.prototype.addEffects = function (div) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;
  var ss = this.format.getSelectionState();

  div.style.paddingTop = '0px';
  div.style.paddingBottom = '2px';

  var table = document.createElement('table');

  if (mxClient.IS_QUIRKS) {
    table.style.fontSize = '1em';
  }

  table.style.width = '100%';
  table.style.fontWeight = 'bold';
  table.style.paddingRight = '20px';
  var tbody = document.createElement('tbody');
  var row = document.createElement('tr');
  row.style.padding = '0px';
  var left = document.createElement('td');
  left.style.padding = '0px';
  left.style.width = '50%';
  left.setAttribute('valign', 'top');

  var right = left.cloneNode(true);
  right.style.paddingLeft = '8px';
  row.appendChild(left);
  row.appendChild(right);
  tbody.appendChild(row);
  table.appendChild(tbody);
  div.appendChild(table);

  var current = left;
  var count = 0;

  var addOption = mxUtils.bind(this, function (label, key, defaultValue) {
    var opt = this.createCellOption(label, key, defaultValue);
    opt.style.width = '100%';
    current.appendChild(opt);
    current = current == left ? right : left;
    count++;
  });

  var listener = mxUtils.bind(this, function (sender, evt, force) {
    ss = this.format.getSelectionState();

    left.innerHTML = '';
    right.innerHTML = '';
    current = left;

    if (ss.rounded) {
      addOption(mxResources.get('rounded'), mxConstants.STYLE_ROUNDED, 0);
    }

    if (ss.style.shape == 'swimlane') {
      addOption(mxResources.get('divider'), 'swimlaneLine', 1);
    }

    if (!ss.containsImage) {
      addOption(mxResources.get('shadow'), mxConstants.STYLE_SHADOW, 0);
    }

    if (ss.glass) {
      addOption(mxResources.get('glass'), mxConstants.STYLE_GLASS, 0);
    }

    addOption(mxResources.get('sketch'), 'sketch', 0);
  });

  graph.getModel().addListener(mxEvent.CHANGE, listener);
  this.listeners.push({
    destroy: function () {
      graph.getModel().removeListener(listener);
    },
  });
  listener();

  return div;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
StyleFormatPanel.prototype.addStyleOps = function (div) {
  div.style.paddingTop = '10px';
  div.style.paddingBottom = '10px';

  var btn = mxUtils.button(
    mxResources.get('setAsDefaultStyle'),
    mxUtils.bind(this, function (evt) {
      this.editorUi.actions.get('setAsDefaultStyle').funct();
    })
  );

  btn.setAttribute(
    'title',
    mxResources.get('setAsDefaultStyle') + ' (' + this.editorUi.actions.get('setAsDefaultStyle').shortcut + ')'
  );
  btn.style.width = '202px';
  div.appendChild(btn);

  return div;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramStylePanel = function (format, editorUi, container) {
  BaseFormatPanel.call(this, format, editorUi, container);
  this.init();
};

mxUtils.extend(DiagramStylePanel, BaseFormatPanel);

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramStylePanel.prototype.init = function () {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;

  this.container.appendChild(this.addView(this.createPanel()));
};

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramStylePanel.prototype.addView = function (div) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;
  var model = graph.getModel();

  div.style.whiteSpace = 'normal';

  var sketch = graph.currentVertexStyle['sketch'] == '1' && graph.currentEdgeStyle['sketch'] == '1';
  var rounded = graph.currentVertexStyle['rounded'] == '1';
  var curved = graph.currentEdgeStyle['curved'] == '1';

  var opts = document.createElement('div');
  opts.style.paddingBottom = '12px';
  opts.style.marginRight = '16px';
  div.style.paddingTop = '8px';

  var table = document.createElement('table');

  if (mxClient.IS_QUIRKS) {
    table.style.fontSize = '1em';
  }

  table.style.width = '100%';
  table.style.fontWeight = 'bold';

  var tbody = document.createElement('tbody');
  var row = document.createElement('tr');
  row.style.padding = '0px';

  var left = document.createElement('td');
  left.style.padding = '0px';
  left.style.width = '50%';
  left.setAttribute('valign', 'middle');

  var right = left.cloneNode(true);
  right.style.paddingLeft = '8px';
  row.appendChild(left);
  row.appendChild(right);
  tbody.appendChild(row);
  table.appendChild(tbody);

  // Sketch
  left.appendChild(
    this.createOption(
      mxResources.get('sketch'),
      function () {
        return sketch;
      },
      function (checked) {
        sketch = checked;

        if (checked) {
          graph.currentEdgeStyle['sketch'] = '1';
          graph.currentVertexStyle['sketch'] = '1';
        } else {
          delete graph.currentEdgeStyle['sketch'];
          delete graph.currentVertexStyle['sketch'];
        }

        graph.updateCellStyles('sketch', checked ? '1' : null, graph.getVerticesAndEdges());
      },
      null,
      function (div) {
        div.style.width = 'auto';
      }
    )
  );

  // Rounded
  right.appendChild(
    this.createOption(
      mxResources.get('rounded'),
      function () {
        return rounded;
      },
      function (checked) {
        rounded = checked;

        if (checked) {
          graph.currentVertexStyle['rounded'] = '1';
        } else {
          delete graph.currentVertexStyle['rounded'];
        }

        graph.updateCellStyles('rounded', checked ? '1' : null, graph.getVerticesAndEdges(true, true));
      },
      null,
      function (div) {
        div.style.width = 'auto';
      }
    )
  );

  // Curved
  left = left.cloneNode(false);
  right = right.cloneNode(false);
  row = row.cloneNode(false);
  row.appendChild(left);
  row.appendChild(right);
  tbody.appendChild(row);

  left.appendChild(
    this.createOption(
      mxResources.get('curved'),
      function () {
        return curved;
      },
      function (checked) {
        curved = checked;

        if (checked) {
          graph.currentEdgeStyle['curved'] = '1';
        } else {
          delete graph.currentEdgeStyle['curved'];
        }

        graph.updateCellStyles('curved', checked ? '1' : null, graph.getVerticesAndEdges(false, true));
      },
      null,
      function (div) {
        div.style.width = 'auto';
      }
    )
  );

  opts.appendChild(table);
  div.appendChild(opts);

  var defaultStyles = ['fillColor', 'strokeColor', 'fontColor', 'gradientColor'];

  var updateCells = mxUtils.bind(this, function (styles, graphStyle) {
    var cells = graph.getVerticesAndEdges();

    model.beginUpdate();
    try {
      for (var i = 0; i < cells.length; i++) {
        var style = graph.getCellStyle(cells[i]);

        // Handles special label background color
        if (style['labelBackgroundColor'] != null) {
          graph.updateCellStyles('labelBackgroundColor', graphStyle != null ? graphStyle.background : null, [cells[i]]);
        }

        var edge = model.isEdge(cells[i]);
        var newStyle = model.getStyle(cells[i]);
        var current = edge ? graph.currentEdgeStyle : graph.currentVertexStyle;

        for (var j = 0; j < styles.length; j++) {
          if (
            (style[styles[j]] != null && style[styles[j]] != mxConstants.NONE) ||
            (styles[j] != mxConstants.STYLE_FILLCOLOR && styles[j] != mxConstants.STYLE_STROKECOLOR)
          ) {
            newStyle = mxUtils.setStyle(newStyle, styles[j], current[styles[j]]);
          }
        }

        model.setStyle(cells[i], newStyle);
      }
    } finally {
      model.endUpdate();
    }
  });

  var removeStyles = mxUtils.bind(this, function (style, styles, defaultStyle) {
    if (style != null) {
      for (var j = 0; j < styles.length; j++) {
        if (
          (style[styles[j]] != null && style[styles[j]] != mxConstants.NONE) ||
          (styles[j] != mxConstants.STYLE_FILLCOLOR && styles[j] != mxConstants.STYLE_STROKECOLOR)
        ) {
          style[styles[j]] = defaultStyle[styles[j]];
        }
      }
    }
  });

  var applyStyle = mxUtils.bind(this, function (style, result, cell, graphStyle, theGraph) {
    if (style != null) {
      if (cell != null) {
        // Handles special label background color
        if (result['labelBackgroundColor'] != null) {
          var bg = graphStyle != null ? graphStyle.background : null;
          theGraph = theGraph != null ? theGraph : graph;

          if (bg == null) {
            bg = theGraph.background;
          }

          if (bg == null) {
            bg = theGraph.defaultPageBackgroundColor;
          }

          result['labelBackgroundColor'] = bg;
        }
      }

      for (var key in style) {
        if (
          cell == null ||
          (result[key] != null && result[key] != mxConstants.NONE) ||
          (key != mxConstants.STYLE_FILLCOLOR && key != mxConstants.STYLE_STROKECOLOR)
        ) {
          result[key] = style[key];
        }
      }
    }
  });

  var btn = mxUtils.button(
    mxResources.get('reset'),
    mxUtils.bind(this, function (evt) {
      var all = graph.getVerticesAndEdges(true, true);

      if (all.length > 0) {
        model.beginUpdate();
        try {
          graph.updateCellStyles('sketch', null, all);
          graph.updateCellStyles('rounded', null, all);
          graph.updateCellStyles('curved', null, graph.getVerticesAndEdges(false, true));
        } finally {
          model.endUpdate();
        }
      }

      graph.defaultVertexStyle = mxUtils.clone(ui.initialDefaultVertexStyle);
      graph.defaultEdgeStyle = mxUtils.clone(ui.initialDefaultEdgeStyle);
      ui.clearDefaultStyle();
    })
  );

  btn.setAttribute('title', mxResources.get('reset'));
  btn.style.textOverflow = 'ellipsis';
  btn.style.maxWidth = '90px';
  right.appendChild(btn);

  var createPreview = mxUtils.bind(this, function (commonStyle, vertexStyle, edgeStyle, graphStyle, container) {
    // Wrapper needed to catch events
    var div = document.createElement('div');
    div.style.cssText =
      'position:absolute;display:inline-block;width:100%;height:100%;overflow:hidden;pointer-events:none;';
    container.appendChild(div);

    var graph2 = new Graph(div, null, null, graph.getStylesheet());
    graph2.resetViewOnRootChange = false;
    graph2.foldingEnabled = false;
    graph2.gridEnabled = false;
    graph2.autoScroll = false;
    graph2.setTooltips(false);
    graph2.setConnectable(false);
    graph2.setPanning(false);
    graph2.setEnabled(false);

    graph2.getCellStyle = function (cell) {
      var result = mxUtils.clone(Graph.prototype.getCellStyle.apply(this, arguments));
      var defaultStyle = graph.stylesheet.getDefaultVertexStyle();
      var appliedStyle = vertexStyle;

      if (model.isEdge(cell)) {
        defaultStyle = graph.stylesheet.getDefaultEdgeStyle();
        appliedStyle = edgeStyle;
      }

      removeStyles(result, defaultStyles, defaultStyle);
      applyStyle(commonStyle, result, cell, graphStyle, graph2);
      applyStyle(appliedStyle, result, cell, graphStyle, graph2);

      return result;
    };

    // Avoid HTML labels to capture events in bubble phase
    graph2.model.beginUpdate();
    try {
      var v1 = graph2.insertVertex(graph2.getDefaultParent(), null, 'Shape', 14, 8, 70, 40, 'strokeWidth=2;');
      var e1 = graph2.insertEdge(
        graph2.getDefaultParent(),
        null,
        'Connector',
        v1,
        v1,
        'edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;endSize=5;strokeWidth=2;'
      );
      e1.geometry.points = [new mxPoint(32, 70)];
      e1.geometry.offset = new mxPoint(0, 8);
    } finally {
      graph2.model.endUpdate();
    }
  });

  // Entries
  var entries = document.createElement('div');
  entries.style.position = 'relative';
  div.appendChild(entries);

  // Cached entries
  if (this.format.cachedStyleEntries == null) {
    this.format.cachedStyleEntries = [];
  }

  var addEntry = mxUtils.bind(this, function (commonStyle, vertexStyle, edgeStyle, graphStyle, index) {
    var panel = this.format.cachedStyleEntries[index];

    if (panel == null) {
      panel = document.createElement('div');
      panel.style.cssText =
        'display:inline-block;position:relative;width:96px;height:90px;' +
        'cursor:pointer;border:1px solid gray;margin:2px;overflow:hidden;';

      if (graphStyle != null && graphStyle.background != null) {
        panel.style.backgroundColor = graphStyle.background;
      }

      createPreview(commonStyle, vertexStyle, edgeStyle, graphStyle, panel);

      mxEvent.addGestureListeners(
        panel,
        mxUtils.bind(this, function (evt) {
          panel.style.opacity = 0.5;
        }),
        null,
        mxUtils.bind(this, function (evt) {
          panel.style.opacity = 1;
          graph.defaultVertexStyle = mxUtils.clone(ui.initialDefaultVertexStyle);
          graph.defaultEdgeStyle = mxUtils.clone(ui.initialDefaultEdgeStyle);

          applyStyle(commonStyle, graph.defaultVertexStyle);
          applyStyle(commonStyle, graph.defaultEdgeStyle);
          applyStyle(vertexStyle, graph.defaultVertexStyle);
          applyStyle(edgeStyle, graph.defaultEdgeStyle);
          ui.clearDefaultStyle();

          if (sketch) {
            graph.currentEdgeStyle['sketch'] = '1';
            graph.currentVertexStyle['sketch'] = '1';
          } else {
            graph.currentEdgeStyle['sketch'] = '0';
            graph.currentVertexStyle['sketch'] = '0';
          }

          if (rounded) {
            graph.currentVertexStyle['rounded'] = '1';
            graph.currentEdgeStyle['rounded'] = '1';
          } else {
            graph.currentVertexStyle['rounded'] = '0';
            graph.currentEdgeStyle['rounded'] = '1';
          }

          if (curved) {
            graph.currentEdgeStyle['curved'] = '1';
          } else {
            graph.currentEdgeStyle['curved'] = '0';
          }

          model.beginUpdate();
          try {
            updateCells(defaultStyles, graphStyle);

            var change = new ChangePageSetup(ui, graphStyle != null ? graphStyle.background : null);
            change.ignoreImage = true;
            model.execute(change);

            model.execute(
              new ChangeGridColor(
                ui,
                graphStyle != null && graphStyle.gridColor != null ? graphStyle.gridColor : graph.view.defaultGridColor
              )
            );
          } finally {
            model.endUpdate();
          }
        })
      );

      mxEvent.addListener(
        panel,
        'mouseenter',
        mxUtils.bind(this, function (evt) {
          var prev = graph.getCellStyle;
          var prevBg = graph.background;
          var prevGrid = graph.view.gridColor;

          graph.background = graphStyle != null ? graphStyle.background : null;
          graph.view.gridColor =
            graphStyle != null && graphStyle.gridColor != null ? graphStyle.gridColor : graph.view.defaultGridColor;

          graph.getCellStyle = function (cell) {
            var result = mxUtils.clone(prev.apply(this, arguments));

            var defaultStyle = graph.stylesheet.getDefaultVertexStyle();
            var appliedStyle = vertexStyle;

            if (model.isEdge(cell)) {
              defaultStyle = graph.stylesheet.getDefaultEdgeStyle();
              appliedStyle = edgeStyle;
            }

            removeStyles(result, defaultStyles, defaultStyle);
            applyStyle(commonStyle, result, cell, graphStyle);
            applyStyle(appliedStyle, result, cell, graphStyle);

            return result;
          };

          graph.refresh();
          graph.getCellStyle = prev;
          graph.background = prevBg;
          graph.view.gridColor = prevGrid;
        })
      );

      mxEvent.addListener(
        panel,
        'mouseleave',
        mxUtils.bind(this, function (evt) {
          graph.refresh();
        })
      );

      this.format.cachedStyleEntries[index] = panel;
    }

    entries.appendChild(panel);
  });

  // Maximum palettes to switch the switcher
  var maxEntries = 10;
  var pageCount = Math.ceil(Editor.styles.length / maxEntries);
  this.format.currentStylePage = this.format.currentStylePage != null ? this.format.currentStylePage : 0;
  var dots = [];

  var addEntries = mxUtils.bind(this, function () {
    if (dots.length > 0) {
      dots[this.format.currentStylePage].style.background = '#84d7ff';
    }

    for (
      var i = this.format.currentStylePage * maxEntries;
      i < Math.min((this.format.currentStylePage + 1) * maxEntries, Editor.styles.length);
      i++
    ) {
      var s = Editor.styles[i];
      addEntry(s.commonStyle, s.vertexStyle, s.edgeStyle, s.graph, i);
    }
  });

  var selectPage = mxUtils.bind(this, function (index) {
    if (index >= 0 && index < pageCount) {
      dots[this.format.currentStylePage].style.background = 'transparent';
      entries.innerHTML = '';
      this.format.currentStylePage = index;
      addEntries();
    }
  });

  if (pageCount > 1) {
    // Selector
    var switcher = document.createElement('div');
    switcher.style.whiteSpace = 'nowrap';
    switcher.style.position = 'relative';
    switcher.style.textAlign = 'center';
    switcher.style.paddingTop = '4px';
    switcher.style.width = '210px';

    div.style.paddingBottom = '8px';

    for (var i = 0; i < pageCount; i++) {
      var dot = document.createElement('div');
      dot.style.display = 'inline-block';
      dot.style.width = '6px';
      dot.style.height = '6px';
      dot.style.marginLeft = '4px';
      dot.style.marginRight = '3px';
      dot.style.borderRadius = '3px';
      dot.style.cursor = 'pointer';
      dot.style.background = 'transparent';
      dot.style.border = '1px solid #b5b6b7';

      mxUtils.bind(this, function (index, elt) {
        mxEvent.addListener(
          dot,
          'click',
          mxUtils.bind(this, function () {
            selectPage(index);
          })
        );
      })(i, dot);

      switcher.appendChild(dot);
      dots.push(dot);
    }

    div.appendChild(switcher);
    addEntries();

    if (pageCount < 15) {
      var left = document.createElement('div');
      left.style.cssText =
        'position:absolute;left:0px;top:4px;bottom:0px;width:20px;margin:0px;opacity:0.5;' +
        'background-repeat:no-repeat;background-position:center center;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAIVBMVEUAAAB2dnZ4eHh3d3d1dXVxcXF2dnZ2dnZ2dnZxcXF2dnYmb3w1AAAACnRSTlMAfCTkhhvb7cQSPH2JPgAAADRJREFUCNdjwACMAmBKaiGYs2oJmLPKAZ3DabU8AMRTXpUKopislqFyVzCAuUZgikkBZjoAcMYLnp53P/UAAAAASUVORK5CYII=);';

      mxEvent.addListener(
        left,
        'click',
        mxUtils.bind(this, function () {
          selectPage(mxUtils.mod(this.format.currentStylePage - 1, pageCount));
        })
      );

      var right = document.createElement('div');
      right.style.cssText =
        'position:absolute;right:2px;top:4px;bottom:0px;width:20px;margin:0px;opacity:0.5;' +
        'background-repeat:no-repeat;background-position:center center;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAQBAMAAADQT4M0AAAAIVBMVEUAAAB2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnYBuwCcAAAACnRSTlMAfCTkhhvb7cQSPH2JPgAAADZJREFUCNdjQAOMAmBKaiGY8loF5rKswsZlrVo8AUiFrTICcbIWK8A5DF1gDoMymMPApIAwHwCS0Qx/U7qCBQAAAABJRU5ErkJggg==);';
      switcher.appendChild(left);
      switcher.appendChild(right);

      mxEvent.addListener(
        right,
        'click',
        mxUtils.bind(this, function () {
          selectPage(mxUtils.mod(this.format.currentStylePage + 1, pageCount));
        })
      );

      // Hover state
      function addHoverState(elt) {
        mxEvent.addListener(elt, 'mouseenter', function () {
          elt.style.opacity = '1';
        });
        mxEvent.addListener(elt, 'mouseleave', function () {
          elt.style.opacity = '0.5';
        });
      }

      addHoverState(left);
      addHoverState(right);
    }
  } else {
    addEntries();
  }

  return div;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramFormatPanel = function (format, editorUi, container) {
  BaseFormatPanel.call(this, format, editorUi, container);
  this.init();
};

mxUtils.extend(DiagramFormatPanel, BaseFormatPanel);

/**
 * Switch to disable page view.
 */
DiagramFormatPanel.showPageView = true;

/**
 * Specifies if the background image option should be shown. Default is true.
 */
DiagramFormatPanel.prototype.showBackgroundImageOption = true;

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramFormatPanel.prototype.init = function () {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;

  this.container.appendChild(this.addView(this.createPanel()));

  if (graph.isEnabled()) {
    this.container.appendChild(this.addOptions(this.createPanel()));
    this.container.appendChild(this.addPaperSize(this.createPanel()));
    this.container.appendChild(this.addStyleOps(this.createPanel()));
  }
};

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramFormatPanel.prototype.addView = function (div) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;

  div.appendChild(this.createTitle(mxResources.get('view')));

  // Grid
  this.addGridOption(div);

  // Page View
  if (DiagramFormatPanel.showPageView) {
    div.appendChild(
      this.createOption(
        mxResources.get('pageView'),
        function () {
          return graph.pageVisible;
        },
        function (checked) {
          ui.actions.get('pageView').funct();
        },
        {
          install: function (apply) {
            this.listener = function () {
              apply(graph.pageVisible);
            };

            ui.addListener('pageViewChanged', this.listener);
          },
          destroy: function () {
            ui.removeListener(this.listener);
          },
        }
      )
    );
  }

  if (graph.isEnabled()) {
    // Background
    var bg = this.createColorOption(
      mxResources.get('background'),
      function () {
        return graph.background;
      },
      function (color) {
        var change = new ChangePageSetup(ui, color);
        change.ignoreImage = true;

        graph.model.execute(change);
      },
      '#ffffff',
      {
        install: function (apply) {
          this.listener = function () {
            apply(graph.background);
          };

          ui.addListener('backgroundColorChanged', this.listener);
        },
        destroy: function () {
          ui.removeListener(this.listener);
        },
      }
    );

    if (this.showBackgroundImageOption) {
      var btn = mxUtils.button(mxResources.get('image'), function (evt) {
        ui.showBackgroundImageDialog(null, ui.editor.graph.backgroundImage);
        mxEvent.consume(evt);
      });

      btn.style.position = 'absolute';
      btn.className = 'geColorBtn';
      btn.style.marginTop = '-4px';
      btn.style.paddingBottom = document.documentMode == 11 || mxClient.IS_MT ? '0px' : '2px';
      btn.style.height = '22px';
      btn.style.right = mxClient.IS_QUIRKS ? '52px' : '72px';
      btn.style.width = '56px';

      bg.appendChild(btn);
    }

    div.appendChild(bg);
  }

  return div;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramFormatPanel.prototype.addOptions = function (div) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;

  div.appendChild(this.createTitle(mxResources.get('options')));

  if (graph.isEnabled()) {
    // Connection arrows
    div.appendChild(
      this.createOption(
        mxResources.get('connectionArrows'),
        function () {
          return graph.connectionArrowsEnabled;
        },
        function (checked) {
          ui.actions.get('connectionArrows').funct();
        },
        {
          install: function (apply) {
            this.listener = function () {
              apply(graph.connectionArrowsEnabled);
            };

            ui.addListener('connectionArrowsChanged', this.listener);
          },
          destroy: function () {
            ui.removeListener(this.listener);
          },
        }
      )
    );

    // Connection points
    div.appendChild(
      this.createOption(
        mxResources.get('connectionPoints'),
        function () {
          return graph.connectionHandler.isEnabled();
        },
        function (checked) {
          ui.actions.get('connectionPoints').funct();
        },
        {
          install: function (apply) {
            this.listener = function () {
              apply(graph.connectionHandler.isEnabled());
            };

            ui.addListener('connectionPointsChanged', this.listener);
          },
          destroy: function () {
            ui.removeListener(this.listener);
          },
        }
      )
    );

    // Guides
    div.appendChild(
      this.createOption(
        mxResources.get('guides'),
        function () {
          return graph.graphHandler.guidesEnabled;
        },
        function (checked) {
          ui.actions.get('guides').funct();
        },
        {
          install: function (apply) {
            this.listener = function () {
              apply(graph.graphHandler.guidesEnabled);
            };

            ui.addListener('guidesEnabledChanged', this.listener);
          },
          destroy: function () {
            ui.removeListener(this.listener);
          },
        }
      )
    );
  }

  return div;
};

/**
 *
 */
DiagramFormatPanel.prototype.addGridOption = function (container) {
  var fPanel = this;
  var ui = this.editorUi;
  var graph = ui.editor.graph;

  var input = document.createElement('input');
  input.style.position = 'absolute';
  input.style.textAlign = 'right';
  input.style.width = '38px';
  input.value = this.inUnit(graph.getGridSize()) + ' ' + this.getUnit();

  var stepper = this.createStepper(input, update, this.getUnitStep(), null, null, null, this.isFloatUnit());
  input.style.display = graph.isGridEnabled() ? '' : 'none';
  stepper.style.display = input.style.display;

  mxEvent.addListener(input, 'keydown', function (e) {
    if (e.keyCode == 13) {
      graph.container.focus();
      mxEvent.consume(e);
    } else if (e.keyCode == 27) {
      input.value = graph.getGridSize();
      graph.container.focus();
      mxEvent.consume(e);
    }
  });

  function update(evt) {
    var value = fPanel.isFloatUnit() ? parseFloat(input.value) : parseInt(input.value);
    value = fPanel.fromUnit(Math.max(fPanel.inUnit(1), isNaN(value) ? fPanel.inUnit(10) : value));

    if (value != graph.getGridSize()) {
      graph.setGridSize(value);
    }

    input.value = fPanel.inUnit(value) + ' ' + fPanel.getUnit();
    mxEvent.consume(evt);
  }

  mxEvent.addListener(input, 'blur', update);
  mxEvent.addListener(input, 'change', update);

  var unitChangeListener = function (sender, evt) {
    input.value = fPanel.inUnit(graph.getGridSize()) + ' ' + fPanel.getUnit();
    fPanel.format.refresh();
  };

  graph.view.addListener('unitChanged', unitChangeListener);
  this.listeners.push({
    destroy: function () {
      graph.view.removeListener(unitChangeListener);
    },
  });

  if (mxClient.IS_SVG) {
    input.style.marginTop = '-2px';
    input.style.right = '84px';
    stepper.style.marginTop = '-16px';
    stepper.style.right = '72px';

    var panel = this.createColorOption(
      mxResources.get('grid'),
      function () {
        var color = graph.view.gridColor;

        return graph.isGridEnabled() ? color : null;
      },
      function (color) {
        var enabled = graph.isGridEnabled();

        if (color == mxConstants.NONE) {
          graph.setGridEnabled(false);
        } else {
          graph.setGridEnabled(true);
          ui.setGridColor(color);
        }

        input.style.display = graph.isGridEnabled() ? '' : 'none';
        stepper.style.display = input.style.display;

        if (enabled != graph.isGridEnabled()) {
          ui.fireEvent(new mxEventObject('gridEnabledChanged'));
        }
      },
      '#e0e0e0',
      {
        install: function (apply) {
          this.listener = function () {
            apply(graph.isGridEnabled() ? graph.view.gridColor : null);
          };

          ui.addListener('gridColorChanged', this.listener);
          ui.addListener('gridEnabledChanged', this.listener);
        },
        destroy: function () {
          ui.removeListener(this.listener);
        },
      }
    );

    panel.appendChild(input);
    panel.appendChild(stepper);
    container.appendChild(panel);
  } else {
    input.style.marginTop = '2px';
    input.style.right = '32px';
    stepper.style.marginTop = '2px';
    stepper.style.right = '20px';

    container.appendChild(input);
    container.appendChild(stepper);

    container.appendChild(
      this.createOption(
        mxResources.get('grid'),
        function () {
          return graph.isGridEnabled();
        },
        function (checked) {
          graph.setGridEnabled(checked);

          if (graph.isGridEnabled()) {
            graph.view.gridColor = '#e0e0e0';
          }

          ui.fireEvent(new mxEventObject('gridEnabledChanged'));
        },
        {
          install: function (apply) {
            this.listener = function () {
              input.style.display = graph.isGridEnabled() ? '' : 'none';
              stepper.style.display = input.style.display;

              apply(graph.isGridEnabled());
            };

            ui.addListener('gridEnabledChanged', this.listener);
          },
          destroy: function () {
            ui.removeListener(this.listener);
          },
        }
      )
    );
  }
};

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramFormatPanel.prototype.addDocumentProperties = function (div) {
  // Hook for subclassers
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;

  div.appendChild(this.createTitle(mxResources.get('options')));

  return div;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramFormatPanel.prototype.addPaperSize = function (div) {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;

  div.appendChild(this.createTitle(mxResources.get('paperSize')));

  var accessor = PageSetupDialog.addPageFormatPanel(div, 'formatpanel', graph.pageFormat, function (pageFormat) {
    if (
      graph.pageFormat == null ||
      graph.pageFormat.width != pageFormat.width ||
      graph.pageFormat.height != pageFormat.height
    ) {
      var change = new ChangePageSetup(ui, null, null, pageFormat);
      change.ignoreColor = true;
      change.ignoreImage = true;

      graph.model.execute(change);
    }
  });

  this.addKeyHandler(accessor.widthInput, function () {
    accessor.set(graph.pageFormat);
  });
  this.addKeyHandler(accessor.heightInput, function () {
    accessor.set(graph.pageFormat);
  });

  var listener = function () {
    accessor.set(graph.pageFormat);
  };

  ui.addListener('pageFormatChanged', listener);
  this.listeners.push({
    destroy: function () {
      ui.removeListener(listener);
    },
  });

  graph.getModel().addListener(mxEvent.CHANGE, listener);
  this.listeners.push({
    destroy: function () {
      graph.getModel().removeListener(listener);
    },
  });

  return div;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramFormatPanel.prototype.addStyleOps = function (div) {
  var btn = mxUtils.button(
    mxResources.get('editData'),
    mxUtils.bind(this, function (evt) {
      this.editorUi.actions.get('editData').funct();
    })
  );

  btn.setAttribute('title', mxResources.get('editData') + ' (' + this.editorUi.actions.get('editData').shortcut + ')');
  btn.style.width = '202px';
  btn.style.marginBottom = '2px';
  div.appendChild(btn);

  mxUtils.br(div);

  btn = mxUtils.button(
    mxResources.get('clearDefaultStyle'),
    mxUtils.bind(this, function (evt) {
      this.editorUi.actions.get('clearDefaultStyle').funct();
    })
  );

  btn.setAttribute(
    'title',
    mxResources.get('clearDefaultStyle') + ' (' + this.editorUi.actions.get('clearDefaultStyle').shortcut + ')'
  );
  btn.style.width = '202px';
  div.appendChild(btn);

  return div;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
DiagramFormatPanel.prototype.destroy = function () {
  BaseFormatPanel.prototype.destroy.apply(this, arguments);

  if (this.gridEnabledListener) {
    this.editorUi.removeListener(this.gridEnabledListener);
    this.gridEnabledListener = null;
  }
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 */
/**
 * Registers shapes.
 */
(function () {
  // LATER: Use this to implement striping
  function paintTableBackground(state, c, x, y, w, h, r) {
    if (state != null) {
      var graph = state.view.graph;
      var start = graph.getActualStartSize(state.cell);
      var rows = graph.model.getChildCells(state.cell, true);

      if (rows.length > 0) {
        var events = false;

        if (this.style != null) {
          events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';
        }

        if (!events) {
          c.pointerEvents = false;
        }

        var evenRowColor = mxUtils.getValue(state.style, 'evenRowColor', mxConstants.NONE);
        var oddRowColor = mxUtils.getValue(state.style, 'oddRowColor', mxConstants.NONE);
        var evenColColor = mxUtils.getValue(state.style, 'evenColumnColor', mxConstants.NONE);
        var oddColColor = mxUtils.getValue(state.style, 'oddColumnColor', mxConstants.NONE);
        var cols = graph.model.getChildCells(rows[0], true);

        // Paints column backgrounds
        for (var i = 0; i < cols.length; i++) {
          var clr = mxUtils.mod(i, 2) == 1 ? evenColColor : oddColColor;
          var geo = graph.getCellGeometry(cols[i]);

          if (geo != null && clr != mxConstants.NONE) {
            c.setFillColor(clr);
            c.begin();
            c.moveTo(x + geo.x, y + start.y);

            if (r > 0 && i == cols.length - 1) {
              c.lineTo(x + geo.x + geo.width - r, y);
              c.quadTo(x + geo.x + geo.width, y, x + geo.x + geo.width, y + r);
              c.lineTo(x + geo.x + geo.width, y + h - r);
              c.quadTo(x + geo.x + geo.width, y + h, x + geo.x + geo.width - r, y + h);
            } else {
              c.lineTo(x + geo.x + geo.width, y + start.y);
              c.lineTo(x + geo.x + geo.width, y + h - start.height);
            }

            c.lineTo(x + geo.x, y + h);
            c.close();
            c.fill();
          }
        }

        // Paints row backgrounds
        for (var i = 0; i < rows.length; i++) {
          var clr = mxUtils.mod(i, 2) == 1 ? evenRowColor : oddRowColor;
          var geo = graph.getCellGeometry(rows[i]);

          if (geo != null && clr != mxConstants.NONE) {
            var b = i == rows.length - 1 ? y + h : y + geo.y + geo.height;
            c.setFillColor(clr);

            c.begin();
            c.moveTo(x + start.x, y + geo.y);
            c.lineTo(x + w - start.width, y + geo.y);

            if (r > 0 && i == rows.length - 1) {
              c.lineTo(x + w, b - r);
              c.quadTo(x + w, b, x + w - r, b);
              c.lineTo(x + r, b);
              c.quadTo(x, b, x, b - r);
            } else {
              c.lineTo(x + w - start.width, b);
              c.lineTo(x + start.x, b);
            }

            c.close();
            c.fill();
          }
        }
      }
    }
  }

  // Table Shape
  function TableShape() {
    mxSwimlane.call(this);
  }

  mxUtils.extend(TableShape, mxSwimlane);

  TableShape.prototype.getLabelBounds = function (rect) {
    var start = this.getTitleSize();

    if (start == 0) {
      return mxShape.prototype.getLabelBounds.apply(this, arguments);
    } else {
      return mxSwimlane.prototype.getLabelBounds.apply(this, arguments);
    }
  };

  TableShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    // LATER: Split background to add striping
    //paintTableBackground(this.state, c, x, y, w, h);

    var start = this.getTitleSize();

    if (start == 0) {
      mxRectangleShape.prototype.paintBackground.apply(this, arguments);
    } else {
      mxSwimlane.prototype.paintVertexShape.apply(this, arguments);
      c.translate(-x, -y);
    }

    this.paintForeground(c, x, y, w, h);
  };

  TableShape.prototype.paintForeground = function (c, x, y, w, h) {
    if (this.state != null) {
      var flipH = this.flipH;
      var flipV = this.flipV;

      if (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH) {
        var tmp = flipH;
        flipH = flipV;
        flipV = tmp;
      }

      // Negative transform to avoid save/restore
      c.rotate(-this.getShapeRotation(), flipH, flipV, x + w / 2, y + h / 2);

      s = this.scale;
      x = this.bounds.x / s;
      y = this.bounds.y / s;
      w = this.bounds.width / s;
      h = this.bounds.height / s;
      this.paintTableForeground(c, x, y, w, h);
    }
  };

  TableShape.prototype.paintTableForeground = function (c, x, y, w, h) {
    var graph = this.state.view.graph;
    var start = graph.getActualStartSize(this.state.cell);
    var rows = graph.model.getChildCells(this.state.cell, true);

    if (rows.length > 0) {
      var rowLines = mxUtils.getValue(this.state.style, 'rowLines', '1') != '0';
      var columnLines = mxUtils.getValue(this.state.style, 'columnLines', '1') != '0';

      // Paints row lines
      if (rowLines) {
        for (var i = 1; i < rows.length; i++) {
          var geo = graph.getCellGeometry(rows[i]);

          if (geo != null) {
            c.begin();
            c.moveTo(x + start.x, y + geo.y);
            c.lineTo(x + w - start.width, y + geo.y);
            c.end();
            c.stroke();
          }
        }
      }

      if (columnLines) {
        var cols = graph.model.getChildCells(rows[0], true);

        // Paints column lines
        for (var i = 1; i < cols.length; i++) {
          var geo = graph.getCellGeometry(cols[i]);

          if (geo != null) {
            c.begin();
            c.moveTo(x + geo.x + start.x, y + start.y);
            c.lineTo(x + geo.x + start.x, y + h - start.height);
            c.end();
            c.stroke();
          }
        }
      }
    }
  };

  mxCellRenderer.registerShape('table', TableShape);

  // Cube Shape, supports size style
  function CubeShape() {
    mxCylinder.call(this);
  }
  mxUtils.extend(CubeShape, mxCylinder);
  CubeShape.prototype.size = 20;
  CubeShape.prototype.darkOpacity = 0;
  CubeShape.prototype.darkOpacity2 = 0;

  CubeShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    var s = Math.max(0, Math.min(w, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)))));
    var op = Math.max(-1, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'darkOpacity', this.darkOpacity))));
    var op2 = Math.max(-1, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'darkOpacity2', this.darkOpacity2))));
    c.translate(x, y);

    c.begin();
    c.moveTo(0, 0);
    c.lineTo(w - s, 0);
    c.lineTo(w, s);
    c.lineTo(w, h);
    c.lineTo(s, h);
    c.lineTo(0, h - s);
    c.lineTo(0, 0);
    c.close();
    c.end();
    c.fillAndStroke();

    if (!this.outline) {
      c.setShadow(false);

      if (op != 0) {
        c.setFillAlpha(Math.abs(op));
        c.setFillColor(op < 0 ? '#FFFFFF' : '#000000');
        c.begin();
        c.moveTo(0, 0);
        c.lineTo(w - s, 0);
        c.lineTo(w, s);
        c.lineTo(s, s);
        c.close();
        c.fill();
      }

      if (op2 != 0) {
        c.setFillAlpha(Math.abs(op2));
        c.setFillColor(op2 < 0 ? '#FFFFFF' : '#000000');
        c.begin();
        c.moveTo(0, 0);
        c.lineTo(s, s);
        c.lineTo(s, h);
        c.lineTo(0, h - s);
        c.close();
        c.fill();
      }

      c.begin();
      c.moveTo(s, h);
      c.lineTo(s, s);
      c.lineTo(0, 0);
      c.moveTo(s, s);
      c.lineTo(w, s);
      c.end();
      c.stroke();
    }
  };
  CubeShape.prototype.getLabelMargins = function (rect) {
    if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
      var s = parseFloat(mxUtils.getValue(this.style, 'size', this.size)) * this.scale;

      return new mxRectangle(s, s, 0, 0);
    }

    return null;
  };

  mxCellRenderer.registerShape('cube', CubeShape);

  var tan30 = Math.tan(mxUtils.toRadians(30));
  var tan30Dx = (0.5 - tan30) / 2;

  // Cube Shape, supports size style
  function IsoRectangleShape() {
    mxActor.call(this);
  }
  mxUtils.extend(IsoRectangleShape, mxActor);
  IsoRectangleShape.prototype.size = 20;
  IsoRectangleShape.prototype.redrawPath = function (path, x, y, w, h) {
    var m = Math.min(w, h / tan30);

    path.translate((w - m) / 2, (h - m) / 2 + m / 4);
    path.moveTo(0, 0.25 * m);
    path.lineTo(0.5 * m, m * tan30Dx);
    path.lineTo(m, 0.25 * m);
    path.lineTo(0.5 * m, (0.5 - tan30Dx) * m);
    path.lineTo(0, 0.25 * m);
    path.close();
    path.end();
  };

  mxCellRenderer.registerShape('isoRectangle', IsoRectangleShape);

  // Cube Shape, supports size style
  function IsoCubeShape() {
    mxCylinder.call(this);
  }
  mxUtils.extend(IsoCubeShape, mxCylinder);
  IsoCubeShape.prototype.size = 20;
  IsoCubeShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
    var m = Math.min(w, h / (0.5 + tan30));

    if (isForeground) {
      path.moveTo(0, 0.25 * m);
      path.lineTo(0.5 * m, (0.5 - tan30Dx) * m);
      path.lineTo(m, 0.25 * m);
      path.moveTo(0.5 * m, (0.5 - tan30Dx) * m);
      path.lineTo(0.5 * m, (1 - tan30Dx) * m);
      path.end();
    } else {
      path.translate((w - m) / 2, (h - m) / 2);
      path.moveTo(0, 0.25 * m);
      path.lineTo(0.5 * m, m * tan30Dx);
      path.lineTo(m, 0.25 * m);
      path.lineTo(m, 0.75 * m);
      path.lineTo(0.5 * m, (1 - tan30Dx) * m);
      path.lineTo(0, 0.75 * m);
      path.close();
      path.end();
    }
  };

  mxCellRenderer.registerShape('isoCube', IsoCubeShape);

  // DataStore Shape, supports size style
  function DataStoreShape() {
    mxCylinder.call(this);
  }
  mxUtils.extend(DataStoreShape, mxCylinder);

  DataStoreShape.prototype.redrawPath = function (c, x, y, w, h, isForeground) {
    var dy = Math.min(h / 2, Math.round(h / 8) + this.strokewidth - 1);

    if ((isForeground && this.fill != null) || (!isForeground && this.fill == null)) {
      c.moveTo(0, dy);
      c.curveTo(0, 2 * dy, w, 2 * dy, w, dy);

      // Needs separate shapes for correct hit-detection
      if (!isForeground) {
        c.stroke();
        c.begin();
      }

      c.translate(0, dy / 2);
      c.moveTo(0, dy);
      c.curveTo(0, 2 * dy, w, 2 * dy, w, dy);

      // Needs separate shapes for correct hit-detection
      if (!isForeground) {
        c.stroke();
        c.begin();
      }

      c.translate(0, dy / 2);
      c.moveTo(0, dy);
      c.curveTo(0, 2 * dy, w, 2 * dy, w, dy);

      // Needs separate shapes for correct hit-detection
      if (!isForeground) {
        c.stroke();
        c.begin();
      }

      c.translate(0, -dy);
    }

    if (!isForeground) {
      c.moveTo(0, dy);
      c.curveTo(0, -dy / 3, w, -dy / 3, w, dy);
      c.lineTo(w, h - dy);
      c.curveTo(w, h + dy / 3, 0, h + dy / 3, 0, h - dy);
      c.close();
    }
  };
  DataStoreShape.prototype.getLabelMargins = function (rect) {
    return new mxRectangle(
      0,
      2.5 * Math.min(rect.height / 2, Math.round(rect.height / 8) + this.strokewidth - 1),
      0,
      0
    );
  };

  mxCellRenderer.registerShape('datastore', DataStoreShape);

  // Note Shape, supports size style
  function NoteShape() {
    mxCylinder.call(this);
  }
  mxUtils.extend(NoteShape, mxCylinder);
  NoteShape.prototype.size = 30;
  NoteShape.prototype.darkOpacity = 0;

  NoteShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    var s = Math.max(0, Math.min(w, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)))));
    var op = Math.max(-1, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'darkOpacity', this.darkOpacity))));
    c.translate(x, y);

    c.begin();
    c.moveTo(0, 0);
    c.lineTo(w - s, 0);
    c.lineTo(w, s);
    c.lineTo(w, h);
    c.lineTo(0, h);
    c.lineTo(0, 0);
    c.close();
    c.end();
    c.fillAndStroke();

    if (!this.outline) {
      c.setShadow(false);

      if (op != 0) {
        c.setFillAlpha(Math.abs(op));
        c.setFillColor(op < 0 ? '#FFFFFF' : '#000000');
        c.begin();
        c.moveTo(w - s, 0);
        c.lineTo(w - s, s);
        c.lineTo(w, s);
        c.close();
        c.fill();
      }

      c.begin();
      c.moveTo(w - s, 0);
      c.lineTo(w - s, s);
      c.lineTo(w, s);
      c.end();
      c.stroke();
    }
  };

  mxCellRenderer.registerShape('note', NoteShape);

  // Note Shape, supports size style
  function NoteShape2() {
    NoteShape.call(this);
  }
  mxUtils.extend(NoteShape2, NoteShape);

  mxCellRenderer.registerShape('note2', NoteShape2);

  // Flexible cube Shape
  function IsoCubeShape2() {
    mxShape.call(this);
  }
  mxUtils.extend(IsoCubeShape2, mxShape);
  IsoCubeShape2.prototype.isoAngle = 15;

  IsoCubeShape2.prototype.paintVertexShape = function (c, x, y, w, h) {
    var isoAngle =
      (Math.max(0.01, Math.min(94, parseFloat(mxUtils.getValue(this.style, 'isoAngle', this.isoAngle)))) * Math.PI) /
      200;
    var isoH = Math.min(w * Math.tan(isoAngle), h * 0.5);

    c.translate(x, y);

    c.begin();
    c.moveTo(w * 0.5, 0);
    c.lineTo(w, isoH);
    c.lineTo(w, h - isoH);
    c.lineTo(w * 0.5, h);
    c.lineTo(0, h - isoH);
    c.lineTo(0, isoH);
    c.close();
    c.fillAndStroke();

    c.setShadow(false);

    c.begin();
    c.moveTo(0, isoH);
    c.lineTo(w * 0.5, 2 * isoH);
    c.lineTo(w, isoH);
    c.moveTo(w * 0.5, 2 * isoH);
    c.lineTo(w * 0.5, h);
    c.stroke();
  };

  mxCellRenderer.registerShape('isoCube2', IsoCubeShape2);

  // (LEGACY) Flexible cylinder Shape
  function CylinderShape() {
    mxShape.call(this);
  }

  mxUtils.extend(CylinderShape, mxShape);

  CylinderShape.prototype.size = 15;

  CylinderShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    var size = Math.max(0, Math.min(h * 0.5, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));

    c.translate(x, y);

    if (size == 0) {
      c.rect(0, 0, w, h);
      c.fillAndStroke();
    } else {
      c.begin();
      c.moveTo(0, size);
      c.arcTo(w * 0.5, size, 0, 0, 1, w * 0.5, 0);
      c.arcTo(w * 0.5, size, 0, 0, 1, w, size);
      c.lineTo(w, h - size);
      c.arcTo(w * 0.5, size, 0, 0, 1, w * 0.5, h);
      c.arcTo(w * 0.5, size, 0, 0, 1, 0, h - size);
      c.close();
      c.fillAndStroke();

      c.setShadow(false);

      c.begin();
      c.moveTo(w, size);
      c.arcTo(w * 0.5, size, 0, 0, 1, w * 0.5, 2 * size);
      c.arcTo(w * 0.5, size, 0, 0, 1, 0, size);
      c.stroke();
    }
  };

  mxCellRenderer.registerShape('cylinder2', CylinderShape);

  // Flexible cylinder3 Shape with offset label
  function CylinderShape3(bounds, fill, stroke, strokewidth) {
    mxShape.call(this);
    this.bounds = bounds;
    this.fill = fill;
    this.stroke = stroke;
    this.strokewidth = strokewidth != null ? strokewidth : 1;
  }

  mxUtils.extend(CylinderShape3, mxCylinder);

  CylinderShape3.prototype.size = 15;

  CylinderShape3.prototype.paintVertexShape = function (c, x, y, w, h) {
    var size = Math.max(0, Math.min(h * 0.5, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var lid = mxUtils.getValue(this.style, 'lid', true);

    c.translate(x, y);

    if (size == 0) {
      c.rect(0, 0, w, h);
      c.fillAndStroke();
    } else {
      c.begin();

      if (lid) {
        c.moveTo(0, size);
        c.arcTo(w * 0.5, size, 0, 0, 1, w * 0.5, 0);
        c.arcTo(w * 0.5, size, 0, 0, 1, w, size);
      } else {
        c.moveTo(0, 0);
        c.arcTo(w * 0.5, size, 0, 0, 0, w * 0.5, size);
        c.arcTo(w * 0.5, size, 0, 0, 0, w, 0);
      }

      c.lineTo(w, h - size);
      c.arcTo(w * 0.5, size, 0, 0, 1, w * 0.5, h);
      c.arcTo(w * 0.5, size, 0, 0, 1, 0, h - size);
      c.close();
      c.fillAndStroke();

      c.setShadow(false);

      if (lid) {
        c.begin();
        c.moveTo(w, size);
        c.arcTo(w * 0.5, size, 0, 0, 1, w * 0.5, 2 * size);
        c.arcTo(w * 0.5, size, 0, 0, 1, 0, size);
        c.stroke();
      }
    }
  };

  mxCellRenderer.registerShape('cylinder3', CylinderShape3);

  // Switch Shape, supports size style
  function SwitchShape() {
    mxActor.call(this);
  }
  mxUtils.extend(SwitchShape, mxActor);
  SwitchShape.prototype.redrawPath = function (c, x, y, w, h) {
    var curve = 0.5;
    c.moveTo(0, 0);
    c.quadTo(w / 2, h * curve, w, 0);
    c.quadTo(w * (1 - curve), h / 2, w, h);
    c.quadTo(w / 2, h * (1 - curve), 0, h);
    c.quadTo(w * curve, h / 2, 0, 0);
    c.end();
  };

  mxCellRenderer.registerShape('switch', SwitchShape);

  // Folder Shape, supports tabWidth, tabHeight styles
  function FolderShape() {
    mxCylinder.call(this);
  }
  mxUtils.extend(FolderShape, mxCylinder);
  FolderShape.prototype.tabWidth = 60;
  FolderShape.prototype.tabHeight = 20;
  FolderShape.prototype.tabPosition = 'right';
  FolderShape.prototype.arcSize = 0.1;

  FolderShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    c.translate(x, y);

    var dx = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'tabWidth', this.tabWidth))));
    var dy = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'tabHeight', this.tabHeight))));
    var tp = mxUtils.getValue(this.style, 'tabPosition', this.tabPosition);
    var rounded = mxUtils.getValue(this.style, 'rounded', false);
    var absArcSize = mxUtils.getValue(this.style, 'absoluteArcSize', false);
    var arcSize = parseFloat(mxUtils.getValue(this.style, 'arcSize', this.arcSize));

    if (!absArcSize) {
      arcSize = Math.min(w, h) * arcSize;
    }

    arcSize = Math.min(arcSize, w * 0.5, (h - dy) * 0.5);

    dx = Math.max(dx, arcSize);
    dx = Math.min(w - arcSize, dx);

    if (!rounded) {
      arcSize = 0;
    }

    c.begin();

    if (tp == 'left') {
      c.moveTo(Math.max(arcSize, 0), dy);
      c.lineTo(Math.max(arcSize, 0), 0);
      c.lineTo(dx, 0);
      c.lineTo(dx, dy);
    }
    // Right is default
    else {
      c.moveTo(w - dx, dy);
      c.lineTo(w - dx, 0);
      c.lineTo(w - Math.max(arcSize, 0), 0);
      c.lineTo(w - Math.max(arcSize, 0), dy);
    }

    if (rounded) {
      c.moveTo(0, arcSize + dy);
      c.arcTo(arcSize, arcSize, 0, 0, 1, arcSize, dy);
      c.lineTo(w - arcSize, dy);
      c.arcTo(arcSize, arcSize, 0, 0, 1, w, arcSize + dy);
      c.lineTo(w, h - arcSize);
      c.arcTo(arcSize, arcSize, 0, 0, 1, w - arcSize, h);
      c.lineTo(arcSize, h);
      c.arcTo(arcSize, arcSize, 0, 0, 1, 0, h - arcSize);
    } else {
      c.moveTo(0, dy);
      c.lineTo(w, dy);
      c.lineTo(w, h);
      c.lineTo(0, h);
    }

    c.close();
    c.fillAndStroke();

    c.setShadow(false);

    var sym = mxUtils.getValue(this.style, 'folderSymbol', null);

    if (sym == 'triangle') {
      c.begin();
      c.moveTo(w - 30, dy + 20);
      c.lineTo(w - 20, dy + 10);
      c.lineTo(w - 10, dy + 20);
      c.close();
      c.stroke();
    }
  };

  mxCellRenderer.registerShape('folder', FolderShape);

  // Folder Shape, supports tabWidth, tabHeight styles
  function UMLStateShape() {
    mxCylinder.call(this);
  }
  mxUtils.extend(UMLStateShape, mxCylinder);
  UMLStateShape.prototype.arcSize = 0.1;

  UMLStateShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    c.translate(x, y);

    //		var dx = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'tabWidth', this.tabWidth))));
    //		var dy = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'tabHeight', this.tabHeight))));
    //		var tp = mxUtils.getValue(this.style, 'tabPosition', this.tabPosition);
    var rounded = mxUtils.getValue(this.style, 'rounded', false);
    var absArcSize = mxUtils.getValue(this.style, 'absoluteArcSize', false);
    var arcSize = parseFloat(mxUtils.getValue(this.style, 'arcSize', this.arcSize));
    var connPoint = mxUtils.getValue(this.style, 'umlStateConnection', null);

    if (!absArcSize) {
      arcSize = Math.min(w, h) * arcSize;
    }

    arcSize = Math.min(arcSize, w * 0.5, h * 0.5);

    if (!rounded) {
      arcSize = 0;
    }

    var dx = 0;

    if (connPoint != null) {
      dx = 10;
    }

    c.begin();
    c.moveTo(dx, arcSize);
    c.arcTo(arcSize, arcSize, 0, 0, 1, dx + arcSize, 0);
    c.lineTo(w - arcSize, 0);
    c.arcTo(arcSize, arcSize, 0, 0, 1, w, arcSize);
    c.lineTo(w, h - arcSize);
    c.arcTo(arcSize, arcSize, 0, 0, 1, w - arcSize, h);
    c.lineTo(dx + arcSize, h);
    c.arcTo(arcSize, arcSize, 0, 0, 1, dx, h - arcSize);
    c.close();
    c.fillAndStroke();

    c.setShadow(false);

    var sym = mxUtils.getValue(this.style, 'umlStateSymbol', null);

    if (sym == 'collapseState') {
      c.roundrect(w - 40, h - 20, 10, 10, 3, 3);
      c.stroke();
      c.roundrect(w - 20, h - 20, 10, 10, 3, 3);
      c.stroke();
      c.begin();
      c.moveTo(w - 30, h - 15);
      c.lineTo(w - 20, h - 15);
      c.stroke();
    }

    if (connPoint == 'connPointRefEntry') {
      c.ellipse(0, h * 0.5 - 10, 20, 20);
      c.fillAndStroke();
    } else if (connPoint == 'connPointRefExit') {
      c.ellipse(0, h * 0.5 - 10, 20, 20);
      c.fillAndStroke();

      c.begin();
      c.moveTo(5, h * 0.5 - 5);
      c.lineTo(15, h * 0.5 + 5);
      c.moveTo(15, h * 0.5 - 5);
      c.lineTo(5, h * 0.5 + 5);
      c.stroke();
    }
  };

  mxCellRenderer.registerShape('umlState', UMLStateShape);

  // Card shape
  function CardShape() {
    mxActor.call(this);
  }
  mxUtils.extend(CardShape, mxActor);
  CardShape.prototype.size = 30;
  CardShape.prototype.isRoundable = function () {
    return true;
  };
  CardShape.prototype.redrawPath = function (c, x, y, w, h) {
    var s = Math.max(0, Math.min(w, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)))));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [new mxPoint(s, 0), new mxPoint(w, 0), new mxPoint(w, h), new mxPoint(0, h), new mxPoint(0, s)],
      this.isRounded,
      arcSize,
      true
    );
    c.end();
  };

  mxCellRenderer.registerShape('card', CardShape);

  // Tape shape
  function TapeShape() {
    mxActor.call(this);
  }
  mxUtils.extend(TapeShape, mxActor);
  TapeShape.prototype.size = 0.4;
  TapeShape.prototype.redrawPath = function (c, x, y, w, h) {
    var dy = h * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var fy = 1.4;

    c.moveTo(0, dy / 2);
    c.quadTo(w / 4, dy * fy, w / 2, dy / 2);
    c.quadTo((w * 3) / 4, dy * (1 - fy), w, dy / 2);
    c.lineTo(w, h - dy / 2);
    c.quadTo((w * 3) / 4, h - dy * fy, w / 2, h - dy / 2);
    c.quadTo(w / 4, h - dy * (1 - fy), 0, h - dy / 2);
    c.lineTo(0, dy / 2);
    c.close();
    c.end();
  };

  TapeShape.prototype.getLabelBounds = function (rect) {
    if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
      var size = mxUtils.getValue(this.style, 'size', this.size);
      var w = rect.width;
      var h = rect.height;

      if (
        this.direction == null ||
        this.direction == mxConstants.DIRECTION_EAST ||
        this.direction == mxConstants.DIRECTION_WEST
      ) {
        var dy = h * size;

        return new mxRectangle(rect.x, rect.y + dy, w, h - 2 * dy);
      } else {
        var dx = w * size;

        return new mxRectangle(rect.x + dx, rect.y, w - 2 * dx, h);
      }
    }

    return rect;
  };

  mxCellRenderer.registerShape('tape', TapeShape);

  // Document shape
  function DocumentShape() {
    mxActor.call(this);
  }
  mxUtils.extend(DocumentShape, mxActor);
  DocumentShape.prototype.size = 0.3;
  DocumentShape.prototype.getLabelMargins = function (rect) {
    if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
      return new mxRectangle(0, 0, 0, parseFloat(mxUtils.getValue(this.style, 'size', this.size)) * rect.height);
    }

    return null;
  };
  DocumentShape.prototype.redrawPath = function (c, x, y, w, h) {
    var dy = h * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var fy = 1.4;

    c.moveTo(0, 0);
    c.lineTo(w, 0);
    c.lineTo(w, h - dy / 2);
    c.quadTo((w * 3) / 4, h - dy * fy, w / 2, h - dy / 2);
    c.quadTo(w / 4, h - dy * (1 - fy), 0, h - dy / 2);
    c.lineTo(0, dy / 2);
    c.close();
    c.end();
  };

  mxCellRenderer.registerShape('document', DocumentShape);

  var cylinderGetCylinderSize = mxCylinder.prototype.getCylinderSize;

  mxCylinder.prototype.getCylinderSize = function (x, y, w, h) {
    var size = mxUtils.getValue(this.style, 'size');

    if (size != null) {
      return h * Math.max(0, Math.min(1, size));
    } else {
      return cylinderGetCylinderSize.apply(this, arguments);
    }
  };

  mxCylinder.prototype.getLabelMargins = function (rect) {
    if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
      var size = mxUtils.getValue(this.style, 'size', 0.15) * 2;

      return new mxRectangle(0, Math.min(this.maxHeight * this.scale, rect.height * size), 0, 0);
    }

    return null;
  };

  CylinderShape3.prototype.getLabelMargins = function (rect) {
    if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
      var size = mxUtils.getValue(this.style, 'size', 15);

      if (!mxUtils.getValue(this.style, 'lid', true)) {
        size /= 2;
      }

      return new mxRectangle(
        0,
        Math.min(rect.height * this.scale, size * 2 * this.scale),
        0,
        Math.max(0, size * 0.3 * this.scale)
      );
    }

    return null;
  };

  FolderShape.prototype.getLabelMargins = function (rect) {
    if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
      var sizeY = mxUtils.getValue(this.style, 'tabHeight', 15) * this.scale;

      if (mxUtils.getValue(this.style, 'labelInHeader', false)) {
        var sizeX = mxUtils.getValue(this.style, 'tabWidth', 15) * this.scale;
        var sizeY = mxUtils.getValue(this.style, 'tabHeight', 15) * this.scale;
        var rounded = mxUtils.getValue(this.style, 'rounded', false);
        var absArcSize = mxUtils.getValue(this.style, 'absoluteArcSize', false);
        var arcSize = parseFloat(mxUtils.getValue(this.style, 'arcSize', this.arcSize));

        if (!absArcSize) {
          arcSize = Math.min(rect.width, rect.height) * arcSize;
        }

        arcSize = Math.min(arcSize, rect.width * 0.5, (rect.height - sizeY) * 0.5);

        if (!rounded) {
          arcSize = 0;
        }

        if (mxUtils.getValue(this.style, 'tabPosition', this.tabPosition) == 'left') {
          return new mxRectangle(
            arcSize,
            0,
            Math.min(rect.width, rect.width - sizeX),
            Math.min(rect.height, rect.height - sizeY)
          );
        } else {
          return new mxRectangle(
            Math.min(rect.width, rect.width - sizeX),
            0,
            arcSize,
            Math.min(rect.height, rect.height - sizeY)
          );
        }
      } else {
        return new mxRectangle(0, Math.min(rect.height, sizeY), 0, 0);
      }
    }

    return null;
  };

  UMLStateShape.prototype.getLabelMargins = function (rect) {
    if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
      var connPoint = mxUtils.getValue(this.style, 'umlStateConnection', null);

      if (connPoint != null) {
        return new mxRectangle(10 * this.scale, 0, 0, 0);
      }
    }

    return null;
  };

  NoteShape2.prototype.getLabelMargins = function (rect) {
    if (mxUtils.getValue(this.style, 'boundedLbl', false)) {
      var size = mxUtils.getValue(this.style, 'size', 15);

      return new mxRectangle(
        0,
        Math.min(rect.height * this.scale, size * this.scale),
        0,
        Math.max(0, size * this.scale)
      );
    }

    return null;
  };

  // Parallelogram shape
  function ParallelogramShape() {
    mxActor.call(this);
  }
  mxUtils.extend(ParallelogramShape, mxActor);
  ParallelogramShape.prototype.size = 0.2;
  ParallelogramShape.prototype.fixedSize = 20;
  ParallelogramShape.prototype.isRoundable = function () {
    return true;
  };
  ParallelogramShape.prototype.redrawPath = function (c, x, y, w, h) {
    var fixed = mxUtils.getValue(this.style, 'fixedSize', '0') != '0';

    var dx = fixed
      ? Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'size', this.fixedSize))))
      : w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [new mxPoint(0, h), new mxPoint(dx, 0), new mxPoint(w, 0), new mxPoint(w - dx, h)],
      this.isRounded,
      arcSize,
      true
    );
    c.end();
  };

  mxCellRenderer.registerShape('parallelogram', ParallelogramShape);

  // Trapezoid shape
  function TrapezoidShape() {
    mxActor.call(this);
  }
  mxUtils.extend(TrapezoidShape, mxActor);
  TrapezoidShape.prototype.size = 0.2;
  TrapezoidShape.prototype.fixedSize = 20;
  TrapezoidShape.prototype.isRoundable = function () {
    return true;
  };
  TrapezoidShape.prototype.redrawPath = function (c, x, y, w, h) {
    var fixed = mxUtils.getValue(this.style, 'fixedSize', '0') != '0';

    var dx = fixed
      ? Math.max(0, Math.min(w * 0.5, parseFloat(mxUtils.getValue(this.style, 'size', this.fixedSize))))
      : w * Math.max(0, Math.min(0.5, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [new mxPoint(0, h), new mxPoint(dx, 0), new mxPoint(w - dx, 0), new mxPoint(w, h)],
      this.isRounded,
      arcSize,
      true
    );
  };

  mxCellRenderer.registerShape('trapezoid', TrapezoidShape);

  // Curly Bracket shape
  function CurlyBracketShape() {
    mxActor.call(this);
  }
  mxUtils.extend(CurlyBracketShape, mxActor);
  CurlyBracketShape.prototype.size = 0.5;
  CurlyBracketShape.prototype.redrawPath = function (c, x, y, w, h) {
    c.setFillColor(null);
    var s = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [
        new mxPoint(w, 0),
        new mxPoint(s, 0),
        new mxPoint(s, h / 2),
        new mxPoint(0, h / 2),
        new mxPoint(s, h / 2),
        new mxPoint(s, h),
        new mxPoint(w, h),
      ],
      this.isRounded,
      arcSize,
      false
    );
    c.end();
  };

  mxCellRenderer.registerShape('curlyBracket', CurlyBracketShape);

  // Parallel marker shape
  function ParallelMarkerShape() {
    mxActor.call(this);
  }
  mxUtils.extend(ParallelMarkerShape, mxActor);
  ParallelMarkerShape.prototype.redrawPath = function (c, x, y, w, h) {
    c.setStrokeWidth(1);
    c.setFillColor(this.stroke);
    var w2 = w / 5;
    c.rect(0, 0, w2, h);
    c.fillAndStroke();
    c.rect(2 * w2, 0, w2, h);
    c.fillAndStroke();
    c.rect(4 * w2, 0, w2, h);
    c.fillAndStroke();
  };

  mxCellRenderer.registerShape('parallelMarker', ParallelMarkerShape);

  /**
   * Adds handJiggle style (jiggle=n sets jiggle)
   */
  function HandJiggle(canvas, defaultVariation) {
    this.canvas = canvas;

    // Avoids "spikes" in the output
    this.canvas.setLineJoin('round');
    this.canvas.setLineCap('round');

    this.defaultVariation = defaultVariation;

    this.originalLineTo = this.canvas.lineTo;
    this.canvas.lineTo = mxUtils.bind(this, HandJiggle.prototype.lineTo);

    this.originalMoveTo = this.canvas.moveTo;
    this.canvas.moveTo = mxUtils.bind(this, HandJiggle.prototype.moveTo);

    this.originalClose = this.canvas.close;
    this.canvas.close = mxUtils.bind(this, HandJiggle.prototype.close);

    this.originalQuadTo = this.canvas.quadTo;
    this.canvas.quadTo = mxUtils.bind(this, HandJiggle.prototype.quadTo);

    this.originalCurveTo = this.canvas.curveTo;
    this.canvas.curveTo = mxUtils.bind(this, HandJiggle.prototype.curveTo);

    this.originalArcTo = this.canvas.arcTo;
    this.canvas.arcTo = mxUtils.bind(this, HandJiggle.prototype.arcTo);
  }

  HandJiggle.prototype.moveTo = function (endX, endY) {
    this.originalMoveTo.apply(this.canvas, arguments);
    this.lastX = endX;
    this.lastY = endY;
    this.firstX = endX;
    this.firstY = endY;
  };

  HandJiggle.prototype.close = function () {
    if (this.firstX != null && this.firstY != null) {
      this.lineTo(this.firstX, this.firstY);
      this.originalClose.apply(this.canvas, arguments);
    }

    this.originalClose.apply(this.canvas, arguments);
  };

  HandJiggle.prototype.quadTo = function (x1, y1, x2, y2) {
    this.originalQuadTo.apply(this.canvas, arguments);
    this.lastX = x2;
    this.lastY = y2;
  };

  HandJiggle.prototype.curveTo = function (x1, y1, x2, y2, x3, y3) {
    this.originalCurveTo.apply(this.canvas, arguments);
    this.lastX = x3;
    this.lastY = y3;
  };

  HandJiggle.prototype.arcTo = function (rx, ry, angle, largeArcFlag, sweepFlag, x, y) {
    this.originalArcTo.apply(this.canvas, arguments);
    this.lastX = x;
    this.lastY = y;
  };

  HandJiggle.prototype.lineTo = function (endX, endY) {
    // LATER: Check why this.canvas.lastX cannot be used
    if (this.lastX != null && this.lastY != null) {
      var dx = Math.abs(endX - this.lastX);
      var dy = Math.abs(endY - this.lastY);
      var dist = Math.sqrt(dx * dx + dy * dy);

      if (dist < 2) {
        this.originalLineTo.apply(this.canvas, arguments);
        this.lastX = endX;
        this.lastY = endY;

        return;
      }

      var segs = Math.round(dist / 10);
      var variation = this.defaultVariation;

      if (segs < 5) {
        segs = 5;
        variation /= 3;
      }

      function sign(x) {
        return typeof x === 'number' ? (x ? (x < 0 ? -1 : 1) : x === x ? 0 : NaN) : NaN;
      }

      var stepX = (sign(endX - this.lastX) * dx) / segs;
      var stepY = (sign(endY - this.lastY) * dy) / segs;

      var fx = dx / dist;
      var fy = dy / dist;

      for (var s = 0; s < segs; s++) {
        var x = stepX * s + this.lastX;
        var y = stepY * s + this.lastY;

        var offset = (Math.random() - 0.5) * variation;
        this.originalLineTo.call(this.canvas, x - offset * fy, y - offset * fx);
      }

      this.originalLineTo.call(this.canvas, endX, endY);
      this.lastX = endX;
      this.lastY = endY;
    } else {
      this.originalLineTo.apply(this.canvas, arguments);
      this.lastX = endX;
      this.lastY = endY;
    }
  };

  HandJiggle.prototype.destroy = function () {
    this.canvas.lineTo = this.originalLineTo;
    this.canvas.moveTo = this.originalMoveTo;
    this.canvas.close = this.originalClose;
    this.canvas.quadTo = this.originalQuadTo;
    this.canvas.curveTo = this.originalCurveTo;
    this.canvas.arcTo = this.originalArcTo;
  };

  // Installs hand jiggle for comic and sketch style
  mxShape.prototype.defaultJiggle = 1.5;

  var shapeBeforePaint = mxShape.prototype.beforePaint;
  mxShape.prototype.beforePaint = function (c) {
    shapeBeforePaint.apply(this, arguments);

    if (c.handJiggle == null) {
      c.handJiggle = this.createHandJiggle(c);
    }
  };

  var shapeAfterPaint = mxShape.prototype.afterPaint;
  mxShape.prototype.afterPaint = function (c) {
    shapeAfterPaint.apply(this, arguments);

    if (c.handJiggle != null) {
      c.handJiggle.destroy();
      delete c.handJiggle;
    }
  };

  // Returns a new HandJiggle canvas
  mxShape.prototype.createComicCanvas = function (c) {
    return new HandJiggle(c, mxUtils.getValue(this.style, 'jiggle', this.defaultJiggle));
  };

  // Overrides to avoid call to rect
  mxShape.prototype.createHandJiggle = function (c) {
    if (!this.outline && this.style != null && mxUtils.getValue(this.style, 'comic', '0') != '0') {
      return this.createComicCanvas(c);
    }

    return null;
  };

  // Sets default jiggle for diamond
  mxRhombus.prototype.defaultJiggle = 2;

  // Overrides to avoid call to rect
  var mxRectangleShapeIsHtmlAllowed0 = mxRectangleShape.prototype.isHtmlAllowed;
  mxRectangleShape.prototype.isHtmlAllowed = function () {
    return (
      !this.outline &&
      (this.style == null ||
        (mxUtils.getValue(this.style, 'comic', '0') == '0' &&
          mxUtils.getValue(this.style, 'sketch', urlParams['rough'] == '1' ? '1' : '0') == '0')) &&
      mxRectangleShapeIsHtmlAllowed0.apply(this, arguments)
    );
  };

  var mxRectangleShapePaintBackground0 = mxRectangleShape.prototype.paintBackground;
  mxRectangleShape.prototype.paintBackground = function (c, x, y, w, h) {
    if (c.handJiggle == null || c.handJiggle.constructor != HandJiggle) {
      mxRectangleShapePaintBackground0.apply(this, arguments);
    } else {
      var events = true;

      if (this.style != null) {
        events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';
      }

      if (
        events ||
        (this.fill != null && this.fill != mxConstants.NONE) ||
        (this.stroke != null && this.stroke != mxConstants.NONE)
      ) {
        if (!events && (this.fill == null || this.fill == mxConstants.NONE)) {
          c.pointerEvents = false;
        }

        c.begin();

        if (this.isRounded) {
          var r = 0;

          if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') {
            r = Math.min(
              w / 2,
              Math.min(h / 2, mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2)
            );
          } else {
            var f =
              mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) /
              100;
            r = Math.min(w * f, h * f);
          }

          c.moveTo(x + r, y);
          c.lineTo(x + w - r, y);
          c.quadTo(x + w, y, x + w, y + r);
          c.lineTo(x + w, y + h - r);
          c.quadTo(x + w, y + h, x + w - r, y + h);
          c.lineTo(x + r, y + h);
          c.quadTo(x, y + h, x, y + h - r);
          c.lineTo(x, y + r);
          c.quadTo(x, y, x + r, y);
        } else {
          c.moveTo(x, y);
          c.lineTo(x + w, y);
          c.lineTo(x + w, y + h);
          c.lineTo(x, y + h);
          c.lineTo(x, y);
        }

        // LATER: Check if close is needed here
        c.close();
        c.end();

        c.fillAndStroke();
      }
    }
  };

  /**
   * Disables glass effect with hand jiggle.
   */
  var mxRectangleShapePaintForeground0 = mxRectangleShape.prototype.paintForeground;
  mxRectangleShape.prototype.paintForeground = function (c, x, y, w, h) {
    if (c.handJiggle == null) {
      mxRectangleShapePaintForeground0.apply(this, arguments);
    }
  };

  // End of hand jiggle integration

  // Process Shape
  function ProcessShape() {
    mxRectangleShape.call(this);
  }
  mxUtils.extend(ProcessShape, mxRectangleShape);
  ProcessShape.prototype.size = 0.1;
  ProcessShape.prototype.fixedSize = false;

  ProcessShape.prototype.isHtmlAllowed = function () {
    return false;
  };
  ProcessShape.prototype.getLabelBounds = function (rect) {
    if (
      mxUtils.getValue(this.state.style, mxConstants.STYLE_HORIZONTAL, true) ==
      (this.direction == null ||
        this.direction == mxConstants.DIRECTION_EAST ||
        this.direction == mxConstants.DIRECTION_WEST)
    ) {
      var w = rect.width;
      var h = rect.height;
      var r = new mxRectangle(rect.x, rect.y, w, h);

      var inset = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));

      if (this.isRounded) {
        var f =
          mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
        inset = Math.max(inset, Math.min(w * f, h * f));
      }

      r.x += Math.round(inset);
      r.width -= Math.round(2 * inset);

      return r;
    }

    return rect;
  };
  ProcessShape.prototype.paintForeground = function (c, x, y, w, h) {
    var isFixedSize = mxUtils.getValue(this.style, 'fixedSize', this.fixedSize);
    var inset = parseFloat(mxUtils.getValue(this.style, 'size', this.size));

    if (isFixedSize) {
      inset = Math.max(0, Math.min(w, inset));
    } else {
      inset = w * Math.max(0, Math.min(1, inset));
    }

    if (this.isRounded) {
      var f =
        mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
      inset = Math.max(inset, Math.min(w * f, h * f));
    }

    // Crisp rendering of inner lines
    inset = Math.round(inset);

    c.begin();
    c.moveTo(x + inset, y);
    c.lineTo(x + inset, y + h);
    c.moveTo(x + w - inset, y);
    c.lineTo(x + w - inset, y + h);
    c.end();
    c.stroke();
    mxRectangleShape.prototype.paintForeground.apply(this, arguments);
  };

  mxCellRenderer.registerShape('process', ProcessShape);

  // Transparent Shape
  function TransparentShape() {
    mxRectangleShape.call(this);
  }
  mxUtils.extend(TransparentShape, mxRectangleShape);
  TransparentShape.prototype.paintBackground = function (c, x, y, w, h) {
    c.setFillColor(mxConstants.NONE);
    c.rect(x, y, w, h);
    c.fill();
  };
  TransparentShape.prototype.paintForeground = function (c, x, y, w, h) {};

  mxCellRenderer.registerShape('transparent', TransparentShape);

  // Callout shape
  function CalloutShape() {
    mxActor.call(this);
  }
  mxUtils.extend(CalloutShape, mxHexagon);
  CalloutShape.prototype.size = 30;
  CalloutShape.prototype.position = 0.5;
  CalloutShape.prototype.position2 = 0.5;
  CalloutShape.prototype.base = 20;
  CalloutShape.prototype.getLabelMargins = function () {
    return new mxRectangle(0, 0, 0, parseFloat(mxUtils.getValue(this.style, 'size', this.size)) * this.scale);
  };
  CalloutShape.prototype.isRoundable = function () {
    return true;
  };
  CalloutShape.prototype.redrawPath = function (c, x, y, w, h) {
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    var s = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var dx = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'position', this.position))));
    var dx2 = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'position2', this.position2))));
    var base = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'base', this.base))));

    this.addPoints(
      c,
      [
        new mxPoint(0, 0),
        new mxPoint(w, 0),
        new mxPoint(w, h - s),
        new mxPoint(Math.min(w, dx + base), h - s),
        new mxPoint(dx2, h),
        new mxPoint(Math.max(0, dx), h - s),
        new mxPoint(0, h - s),
      ],
      this.isRounded,
      arcSize,
      true,
      [4]
    );
  };

  mxCellRenderer.registerShape('callout', CalloutShape);

  // Step shape
  function StepShape() {
    mxActor.call(this);
  }
  mxUtils.extend(StepShape, mxActor);
  StepShape.prototype.size = 0.2;
  StepShape.prototype.fixedSize = 20;
  StepShape.prototype.isRoundable = function () {
    return true;
  };
  StepShape.prototype.redrawPath = function (c, x, y, w, h) {
    var fixed = mxUtils.getValue(this.style, 'fixedSize', '0') != '0';
    var s = fixed
      ? Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'size', this.fixedSize))))
      : w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [
        new mxPoint(0, 0),
        new mxPoint(w - s, 0),
        new mxPoint(w, h / 2),
        new mxPoint(w - s, h),
        new mxPoint(0, h),
        new mxPoint(s, h / 2),
      ],
      this.isRounded,
      arcSize,
      true
    );
    c.end();
  };

  mxCellRenderer.registerShape('step', StepShape);

  // Hexagon shape
  function HexagonShape() {
    mxActor.call(this);
  }
  mxUtils.extend(HexagonShape, mxHexagon);
  HexagonShape.prototype.size = 0.25;
  HexagonShape.prototype.fixedSize = 20;
  HexagonShape.prototype.isRoundable = function () {
    return true;
  };
  HexagonShape.prototype.redrawPath = function (c, x, y, w, h) {
    var fixed = mxUtils.getValue(this.style, 'fixedSize', '0') != '0';
    var s = fixed
      ? Math.max(0, Math.min(w * 0.5, parseFloat(mxUtils.getValue(this.style, 'size', this.fixedSize))))
      : w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [
        new mxPoint(s, 0),
        new mxPoint(w - s, 0),
        new mxPoint(w, 0.5 * h),
        new mxPoint(w - s, h),
        new mxPoint(s, h),
        new mxPoint(0, 0.5 * h),
      ],
      this.isRounded,
      arcSize,
      true
    );
  };

  mxCellRenderer.registerShape('hexagon', HexagonShape);

  // Plus Shape
  function PlusShape() {
    mxRectangleShape.call(this);
  }
  mxUtils.extend(PlusShape, mxRectangleShape);
  PlusShape.prototype.isHtmlAllowed = function () {
    return false;
  };
  PlusShape.prototype.paintForeground = function (c, x, y, w, h) {
    var border = Math.min(w / 5, h / 5) + 1;

    c.begin();
    c.moveTo(x + w / 2, y + border);
    c.lineTo(x + w / 2, y + h - border);
    c.moveTo(x + border, y + h / 2);
    c.lineTo(x + w - border, y + h / 2);
    c.end();
    c.stroke();
    mxRectangleShape.prototype.paintForeground.apply(this, arguments);
  };

  mxCellRenderer.registerShape('plus', PlusShape);

  // Overrides painting of rhombus shape to allow for double style
  var mxRhombusPaintVertexShape = mxRhombus.prototype.paintVertexShape;
  mxRhombus.prototype.getLabelBounds = function (rect) {
    if (this.style['double'] == 1) {
      var margin =
        (Math.max(2, this.strokewidth + 1) * 2 + parseFloat(this.style[mxConstants.STYLE_MARGIN] || 0)) * this.scale;

      return new mxRectangle(rect.x + margin, rect.y + margin, rect.width - 2 * margin, rect.height - 2 * margin);
    }

    return rect;
  };
  mxRhombus.prototype.paintVertexShape = function (c, x, y, w, h) {
    mxRhombusPaintVertexShape.apply(this, arguments);

    if (!this.outline && this.style['double'] == 1) {
      var margin = Math.max(2, this.strokewidth + 1) * 2 + parseFloat(this.style[mxConstants.STYLE_MARGIN] || 0);
      x += margin;
      y += margin;
      w -= 2 * margin;
      h -= 2 * margin;

      if (w > 0 && h > 0) {
        c.setShadow(false);

        // Workaround for closure compiler bug where the lines with x and y above
        // are removed if arguments is used as second argument in call below.
        mxRhombusPaintVertexShape.apply(this, [c, x, y, w, h]);
      }
    }
  };

  // CompositeShape
  function ExtendedShape() {
    mxRectangleShape.call(this);
  }
  mxUtils.extend(ExtendedShape, mxRectangleShape);
  ExtendedShape.prototype.isHtmlAllowed = function () {
    return false;
  };
  ExtendedShape.prototype.getLabelBounds = function (rect) {
    if (this.style['double'] == 1) {
      var margin =
        (Math.max(2, this.strokewidth + 1) + parseFloat(this.style[mxConstants.STYLE_MARGIN] || 0)) * this.scale;

      return new mxRectangle(rect.x + margin, rect.y + margin, rect.width - 2 * margin, rect.height - 2 * margin);
    }

    return rect;
  };

  ExtendedShape.prototype.paintForeground = function (c, x, y, w, h) {
    if (this.style != null) {
      if (!this.outline && this.style['double'] == 1) {
        var margin = Math.max(2, this.strokewidth + 1) + parseFloat(this.style[mxConstants.STYLE_MARGIN] || 0);
        x += margin;
        y += margin;
        w -= 2 * margin;
        h -= 2 * margin;

        if (w > 0 && h > 0) {
          mxRectangleShape.prototype.paintBackground.apply(this, arguments);
        }
      }

      c.setDashed(false);

      // Draws the symbols defined in the style. The symbols are
      // numbered from 1...n. Possible postfixes are align,
      // verticalAlign, spacing, arcSpacing, width, height
      var counter = 0;
      var shape = null;

      do {
        shape = mxCellRenderer.defaultShapes[this.style['symbol' + counter]];

        if (shape != null) {
          var align = this.style['symbol' + counter + 'Align'];
          var valign = this.style['symbol' + counter + 'VerticalAlign'];
          var width = this.style['symbol' + counter + 'Width'];
          var height = this.style['symbol' + counter + 'Height'];
          var spacing = this.style['symbol' + counter + 'Spacing'] || 0;
          var vspacing = this.style['symbol' + counter + 'VSpacing'] || spacing;
          var arcspacing = this.style['symbol' + counter + 'ArcSpacing'];

          if (arcspacing != null) {
            var arcSize = this.getArcSize(w + this.strokewidth, h + this.strokewidth) * arcspacing;
            spacing += arcSize;
            vspacing += arcSize;
          }

          var x2 = x;
          var y2 = y;

          if (align == mxConstants.ALIGN_CENTER) {
            x2 += (w - width) / 2;
          } else if (align == mxConstants.ALIGN_RIGHT) {
            x2 += w - width - spacing;
          } else {
            x2 += spacing;
          }

          if (valign == mxConstants.ALIGN_MIDDLE) {
            y2 += (h - height) / 2;
          } else if (valign == mxConstants.ALIGN_BOTTOM) {
            y2 += h - height - vspacing;
          } else {
            y2 += vspacing;
          }

          c.save();

          // Small hack to pass style along into subshape
          var tmp = new shape();
          // TODO: Clone style and override settings (eg. strokewidth)
          tmp.style = this.style;
          shape.prototype.paintVertexShape.call(tmp, c, x2, y2, width, height);
          c.restore();
        }

        counter++;
      } while (shape != null);
    }

    // Paints glass effect
    mxRectangleShape.prototype.paintForeground.apply(this, arguments);
  };

  mxCellRenderer.registerShape('ext', ExtendedShape);

  // Tape Shape, supports size style
  function MessageShape() {
    mxCylinder.call(this);
  }
  mxUtils.extend(MessageShape, mxCylinder);
  MessageShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
    if (isForeground) {
      path.moveTo(0, 0);
      path.lineTo(w / 2, h / 2);
      path.lineTo(w, 0);
      path.end();
    } else {
      path.moveTo(0, 0);
      path.lineTo(w, 0);
      path.lineTo(w, h);
      path.lineTo(0, h);
      path.close();
    }
  };

  mxCellRenderer.registerShape('message', MessageShape);

  // UML Actor Shape
  function UmlActorShape() {
    mxShape.call(this);
  }
  mxUtils.extend(UmlActorShape, mxShape);
  UmlActorShape.prototype.paintBackground = function (c, x, y, w, h) {
    c.translate(x, y);

    // Head
    c.ellipse(w / 4, 0, w / 2, h / 4);
    c.fillAndStroke();

    c.begin();
    c.moveTo(w / 2, h / 4);
    c.lineTo(w / 2, (2 * h) / 3);

    // Arms
    c.moveTo(w / 2, h / 3);
    c.lineTo(0, h / 3);
    c.moveTo(w / 2, h / 3);
    c.lineTo(w, h / 3);

    // Legs
    c.moveTo(w / 2, (2 * h) / 3);
    c.lineTo(0, h);
    c.moveTo(w / 2, (2 * h) / 3);
    c.lineTo(w, h);
    c.end();

    c.stroke();
  };

  // Replaces existing actor shape
  mxCellRenderer.registerShape('umlActor', UmlActorShape);

  // UML Boundary Shape
  function UmlBoundaryShape() {
    mxShape.call(this);
  }
  mxUtils.extend(UmlBoundaryShape, mxShape);
  UmlBoundaryShape.prototype.getLabelMargins = function (rect) {
    return new mxRectangle(rect.width / 6, 0, 0, 0);
  };
  UmlBoundaryShape.prototype.paintBackground = function (c, x, y, w, h) {
    c.translate(x, y);

    // Base line
    c.begin();
    c.moveTo(0, h / 4);
    c.lineTo(0, (h * 3) / 4);
    c.end();
    c.stroke();

    // Horizontal line
    c.begin();
    c.moveTo(0, h / 2);
    c.lineTo(w / 6, h / 2);
    c.end();
    c.stroke();

    // Circle
    c.ellipse(w / 6, 0, (w * 5) / 6, h);
    c.fillAndStroke();
  };

  // Replaces existing actor shape
  mxCellRenderer.registerShape('umlBoundary', UmlBoundaryShape);

  // UML Entity Shape
  function UmlEntityShape() {
    mxEllipse.call(this);
  }
  mxUtils.extend(UmlEntityShape, mxEllipse);
  UmlEntityShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    mxEllipse.prototype.paintVertexShape.apply(this, arguments);

    c.begin();
    c.moveTo(x + w / 8, y + h);
    c.lineTo(x + (w * 7) / 8, y + h);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('umlEntity', UmlEntityShape);

  // UML Destroy Shape
  function UmlDestroyShape() {
    mxShape.call(this);
  }
  mxUtils.extend(UmlDestroyShape, mxShape);
  UmlDestroyShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    c.translate(x, y);

    c.begin();
    c.moveTo(w, 0);
    c.lineTo(0, h);
    c.moveTo(0, 0);
    c.lineTo(w, h);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('umlDestroy', UmlDestroyShape);

  // UML Control Shape
  function UmlControlShape() {
    mxShape.call(this);
  }
  mxUtils.extend(UmlControlShape, mxShape);
  UmlControlShape.prototype.getLabelBounds = function (rect) {
    return new mxRectangle(rect.x, rect.y + rect.height / 8, rect.width, (rect.height * 7) / 8);
  };
  UmlControlShape.prototype.paintBackground = function (c, x, y, w, h) {
    c.translate(x, y);

    // Upper line
    c.begin();
    c.moveTo((w * 3) / 8, (h / 8) * 1.1);
    c.lineTo((w * 5) / 8, 0);
    c.end();
    c.stroke();

    // Circle
    c.ellipse(0, h / 8, w, (h * 7) / 8);
    c.fillAndStroke();
  };
  UmlControlShape.prototype.paintForeground = function (c, x, y, w, h) {
    // Lower line
    c.begin();
    c.moveTo((w * 3) / 8, (h / 8) * 1.1);
    c.lineTo((w * 5) / 8, h / 4);
    c.end();
    c.stroke();
  };

  // Replaces existing actor shape
  mxCellRenderer.registerShape('umlControl', UmlControlShape);

  // UML Lifeline Shape
  function UmlLifeline() {
    mxRectangleShape.call(this);
  }
  mxUtils.extend(UmlLifeline, mxRectangleShape);
  UmlLifeline.prototype.size = 40;
  UmlLifeline.prototype.isHtmlAllowed = function () {
    return false;
  };
  UmlLifeline.prototype.getLabelBounds = function (rect) {
    var size = Math.max(
      0,
      Math.min(rect.height, parseFloat(mxUtils.getValue(this.style, 'size', this.size)) * this.scale)
    );

    return new mxRectangle(rect.x, rect.y, rect.width, size);
  };
  UmlLifeline.prototype.paintBackground = function (c, x, y, w, h) {
    var size = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var participant = mxUtils.getValue(this.style, 'participant');

    if (participant == null || this.state == null) {
      mxRectangleShape.prototype.paintBackground.call(this, c, x, y, w, size);
    } else {
      var ctor = this.state.view.graph.cellRenderer.getShape(participant);

      if (ctor != null && ctor != UmlLifeline) {
        var shape = new ctor();
        shape.apply(this.state);
        c.save();
        shape.paintVertexShape(c, x, y, w, size);
        c.restore();
      }
    }

    if (size < h) {
      c.setDashed(true);
      c.begin();
      c.moveTo(x + w / 2, y + size);
      c.lineTo(x + w / 2, y + h);
      c.end();
      c.stroke();
    }
  };
  UmlLifeline.prototype.paintForeground = function (c, x, y, w, h) {
    var size = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    mxRectangleShape.prototype.paintForeground.call(this, c, x, y, w, Math.min(h, size));
  };

  mxCellRenderer.registerShape('umlLifeline', UmlLifeline);

  // UML Frame Shape
  function UmlFrame() {
    mxShape.call(this);
  }
  mxUtils.extend(UmlFrame, mxShape);
  UmlFrame.prototype.width = 60;
  UmlFrame.prototype.height = 30;
  UmlFrame.prototype.corner = 10;
  UmlFrame.prototype.getLabelMargins = function (rect) {
    return new mxRectangle(
      0,
      0,
      rect.width - parseFloat(mxUtils.getValue(this.style, 'width', this.width) * this.scale),
      rect.height - parseFloat(mxUtils.getValue(this.style, 'height', this.height) * this.scale)
    );
  };
  UmlFrame.prototype.paintBackground = function (c, x, y, w, h) {
    var co = this.corner;
    var w0 = Math.min(w, Math.max(co, parseFloat(mxUtils.getValue(this.style, 'width', this.width))));
    var h0 = Math.min(h, Math.max(co * 1.5, parseFloat(mxUtils.getValue(this.style, 'height', this.height))));
    var bg = mxUtils.getValue(this.style, mxConstants.STYLE_SWIMLANE_FILLCOLOR, mxConstants.NONE);

    if (bg != mxConstants.NONE) {
      c.setFillColor(bg);
      c.rect(x, y, w, h);
      c.fill();
    }

    if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE) {
      var b = this.getGradientBounds(c, x, y, w, h);
      c.setGradient(this.fill, this.gradient, x, y, w, h, this.gradientDirection);
    } else {
      c.setFillColor(this.fill);
    }

    c.begin();
    c.moveTo(x, y);
    c.lineTo(x + w0, y);
    c.lineTo(x + w0, y + Math.max(0, h0 - co * 1.5));
    c.lineTo(x + Math.max(0, w0 - co), y + h0);
    c.lineTo(x, y + h0);
    c.close();
    c.fillAndStroke();

    c.begin();
    c.moveTo(x + w0, y);
    c.lineTo(x + w, y);
    c.lineTo(x + w, y + h);
    c.lineTo(x, y + h);
    c.lineTo(x, y + h0);
    c.stroke();
  };

  mxCellRenderer.registerShape('umlFrame', UmlFrame);

  mxPerimeter.LifelinePerimeter = function (bounds, vertex, next, orthogonal) {
    var size = UmlLifeline.prototype.size;

    if (vertex != null) {
      size = mxUtils.getValue(vertex.style, 'size', size) * vertex.view.scale;
    }

    var sw = (parseFloat(vertex.style[mxConstants.STYLE_STROKEWIDTH] || 1) * vertex.view.scale) / 2 - 1;

    if (next.x < bounds.getCenterX()) {
      sw += 1;
      sw *= -1;
    }

    return new mxPoint(bounds.getCenterX() + sw, Math.min(bounds.y + bounds.height, Math.max(bounds.y + size, next.y)));
  };

  mxStyleRegistry.putValue('lifelinePerimeter', mxPerimeter.LifelinePerimeter);

  mxPerimeter.OrthogonalPerimeter = function (bounds, vertex, next, orthogonal) {
    orthogonal = true;

    return mxPerimeter.RectanglePerimeter.apply(this, arguments);
  };

  mxStyleRegistry.putValue('orthogonalPerimeter', mxPerimeter.OrthogonalPerimeter);

  mxPerimeter.BackbonePerimeter = function (bounds, vertex, next, orthogonal) {
    var sw = (parseFloat(vertex.style[mxConstants.STYLE_STROKEWIDTH] || 1) * vertex.view.scale) / 2 - 1;

    if (vertex.style['backboneSize'] != null) {
      sw += (parseFloat(vertex.style['backboneSize']) * vertex.view.scale) / 2 - 1;
    }

    if (vertex.style[mxConstants.STYLE_DIRECTION] == 'south' || vertex.style[mxConstants.STYLE_DIRECTION] == 'north') {
      if (next.x < bounds.getCenterX()) {
        sw += 1;
        sw *= -1;
      }

      return new mxPoint(bounds.getCenterX() + sw, Math.min(bounds.y + bounds.height, Math.max(bounds.y, next.y)));
    } else {
      if (next.y < bounds.getCenterY()) {
        sw += 1;
        sw *= -1;
      }

      return new mxPoint(Math.min(bounds.x + bounds.width, Math.max(bounds.x, next.x)), bounds.getCenterY() + sw);
    }
  };

  mxStyleRegistry.putValue('backbonePerimeter', mxPerimeter.BackbonePerimeter);

  // Callout Perimeter
  mxPerimeter.CalloutPerimeter = function (bounds, vertex, next, orthogonal) {
    return mxPerimeter.RectanglePerimeter(
      mxUtils.getDirectedBounds(
        bounds,
        new mxRectangle(
          0,
          0,
          0,
          Math.max(
            0,
            Math.min(
              bounds.height,
              parseFloat(mxUtils.getValue(vertex.style, 'size', CalloutShape.prototype.size)) * vertex.view.scale
            )
          )
        ),
        vertex.style
      ),
      vertex,
      next,
      orthogonal
    );
  };

  mxStyleRegistry.putValue('calloutPerimeter', mxPerimeter.CalloutPerimeter);

  // Parallelogram Perimeter
  mxPerimeter.ParallelogramPerimeter = function (bounds, vertex, next, orthogonal) {
    var fixed = mxUtils.getValue(vertex.style, 'fixedSize', '0') != '0';
    var size = fixed ? ParallelogramShape.prototype.fixedSize : ParallelogramShape.prototype.size;

    if (vertex != null) {
      size = mxUtils.getValue(vertex.style, 'size', size);
    }

    if (fixed) {
      size *= vertex.view.scale;
    }

    var x = bounds.x;
    var y = bounds.y;
    var w = bounds.width;
    var h = bounds.height;

    var direction =
      vertex != null
        ? mxUtils.getValue(vertex.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST)
        : mxConstants.DIRECTION_EAST;
    var vertical = direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH;
    var points;

    if (vertical) {
      var dy = fixed ? Math.max(0, Math.min(h, size)) : h * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x, y),
        new mxPoint(x + w, y + dy),
        new mxPoint(x + w, y + h),
        new mxPoint(x, y + h - dy),
        new mxPoint(x, y),
      ];
    } else {
      var dx = fixed ? Math.max(0, Math.min(w * 0.5, size)) : w * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x + dx, y),
        new mxPoint(x + w, y),
        new mxPoint(x + w - dx, y + h),
        new mxPoint(x, y + h),
        new mxPoint(x + dx, y),
      ];
    }

    var cx = bounds.getCenterX();
    var cy = bounds.getCenterY();

    var p1 = new mxPoint(cx, cy);

    if (orthogonal) {
      if (next.x < x || next.x > x + w) {
        p1.y = next.y;
      } else {
        p1.x = next.x;
      }
    }

    return mxUtils.getPerimeterPoint(points, p1, next);
  };

  mxStyleRegistry.putValue('parallelogramPerimeter', mxPerimeter.ParallelogramPerimeter);

  // Trapezoid Perimeter
  mxPerimeter.TrapezoidPerimeter = function (bounds, vertex, next, orthogonal) {
    var fixed = mxUtils.getValue(vertex.style, 'fixedSize', '0') != '0';
    var size = fixed ? TrapezoidShape.prototype.fixedSize : TrapezoidShape.prototype.size;

    if (vertex != null) {
      size = mxUtils.getValue(vertex.style, 'size', size);
    }

    if (fixed) {
      size *= vertex.view.scale;
    }

    var x = bounds.x;
    var y = bounds.y;
    var w = bounds.width;
    var h = bounds.height;

    var direction =
      vertex != null
        ? mxUtils.getValue(vertex.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST)
        : mxConstants.DIRECTION_EAST;
    var points = [];

    if (direction == mxConstants.DIRECTION_EAST) {
      var dx = fixed ? Math.max(0, Math.min(w * 0.5, size)) : w * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x + dx, y),
        new mxPoint(x + w - dx, y),
        new mxPoint(x + w, y + h),
        new mxPoint(x, y + h),
        new mxPoint(x + dx, y),
      ];
    } else if (direction == mxConstants.DIRECTION_WEST) {
      var dx = fixed ? Math.max(0, Math.min(w, size)) : w * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x, y),
        new mxPoint(x + w, y),
        new mxPoint(x + w - dx, y + h),
        new mxPoint(x + dx, y + h),
        new mxPoint(x, y),
      ];
    } else if (direction == mxConstants.DIRECTION_NORTH) {
      var dy = fixed ? Math.max(0, Math.min(h, size)) : h * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x, y + dy),
        new mxPoint(x + w, y),
        new mxPoint(x + w, y + h),
        new mxPoint(x, y + h - dy),
        new mxPoint(x, y + dy),
      ];
    } else {
      var dy = fixed ? Math.max(0, Math.min(h, size)) : h * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x, y),
        new mxPoint(x + w, y + dy),
        new mxPoint(x + w, y + h - dy),
        new mxPoint(x, y + h),
        new mxPoint(x, y),
      ];
    }

    var cx = bounds.getCenterX();
    var cy = bounds.getCenterY();

    var p1 = new mxPoint(cx, cy);

    if (orthogonal) {
      if (next.x < x || next.x > x + w) {
        p1.y = next.y;
      } else {
        p1.x = next.x;
      }
    }

    return mxUtils.getPerimeterPoint(points, p1, next);
  };

  mxStyleRegistry.putValue('trapezoidPerimeter', mxPerimeter.TrapezoidPerimeter);

  // Step Perimeter
  mxPerimeter.StepPerimeter = function (bounds, vertex, next, orthogonal) {
    var fixed = mxUtils.getValue(vertex.style, 'fixedSize', '0') != '0';
    var size = fixed ? StepShape.prototype.fixedSize : StepShape.prototype.size;

    if (vertex != null) {
      size = mxUtils.getValue(vertex.style, 'size', size);
    }

    if (fixed) {
      size *= vertex.view.scale;
    }

    var x = bounds.x;
    var y = bounds.y;
    var w = bounds.width;
    var h = bounds.height;

    var cx = bounds.getCenterX();
    var cy = bounds.getCenterY();

    var direction =
      vertex != null
        ? mxUtils.getValue(vertex.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST)
        : mxConstants.DIRECTION_EAST;
    var points;

    if (direction == mxConstants.DIRECTION_EAST) {
      var dx = fixed ? Math.max(0, Math.min(w, size)) : w * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x, y),
        new mxPoint(x + w - dx, y),
        new mxPoint(x + w, cy),
        new mxPoint(x + w - dx, y + h),
        new mxPoint(x, y + h),
        new mxPoint(x + dx, cy),
        new mxPoint(x, y),
      ];
    } else if (direction == mxConstants.DIRECTION_WEST) {
      var dx = fixed ? Math.max(0, Math.min(w, size)) : w * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x + dx, y),
        new mxPoint(x + w, y),
        new mxPoint(x + w - dx, cy),
        new mxPoint(x + w, y + h),
        new mxPoint(x + dx, y + h),
        new mxPoint(x, cy),
        new mxPoint(x + dx, y),
      ];
    } else if (direction == mxConstants.DIRECTION_NORTH) {
      var dy = fixed ? Math.max(0, Math.min(h, size)) : h * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x, y + dy),
        new mxPoint(cx, y),
        new mxPoint(x + w, y + dy),
        new mxPoint(x + w, y + h),
        new mxPoint(cx, y + h - dy),
        new mxPoint(x, y + h),
        new mxPoint(x, y + dy),
      ];
    } else {
      var dy = fixed ? Math.max(0, Math.min(h, size)) : h * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x, y),
        new mxPoint(cx, y + dy),
        new mxPoint(x + w, y),
        new mxPoint(x + w, y + h - dy),
        new mxPoint(cx, y + h),
        new mxPoint(x, y + h - dy),
        new mxPoint(x, y),
      ];
    }

    var p1 = new mxPoint(cx, cy);

    if (orthogonal) {
      if (next.x < x || next.x > x + w) {
        p1.y = next.y;
      } else {
        p1.x = next.x;
      }
    }

    return mxUtils.getPerimeterPoint(points, p1, next);
  };

  mxStyleRegistry.putValue('stepPerimeter', mxPerimeter.StepPerimeter);

  // Hexagon Perimeter 2 (keep existing one)
  mxPerimeter.HexagonPerimeter2 = function (bounds, vertex, next, orthogonal) {
    var fixed = mxUtils.getValue(vertex.style, 'fixedSize', '0') != '0';
    var size = fixed ? HexagonShape.prototype.fixedSize : HexagonShape.prototype.size;

    if (vertex != null) {
      size = mxUtils.getValue(vertex.style, 'size', size);
    }

    if (fixed) {
      size *= vertex.view.scale;
    }

    var x = bounds.x;
    var y = bounds.y;
    var w = bounds.width;
    var h = bounds.height;

    var cx = bounds.getCenterX();
    var cy = bounds.getCenterY();

    var direction =
      vertex != null
        ? mxUtils.getValue(vertex.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST)
        : mxConstants.DIRECTION_EAST;
    var vertical = direction == mxConstants.DIRECTION_NORTH || direction == mxConstants.DIRECTION_SOUTH;
    var points;

    if (vertical) {
      var dy = fixed ? Math.max(0, Math.min(h, size)) : h * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(cx, y),
        new mxPoint(x + w, y + dy),
        new mxPoint(x + w, y + h - dy),
        new mxPoint(cx, y + h),
        new mxPoint(x, y + h - dy),
        new mxPoint(x, y + dy),
        new mxPoint(cx, y),
      ];
    } else {
      var dx = fixed ? Math.max(0, Math.min(w, size)) : w * Math.max(0, Math.min(1, size));
      points = [
        new mxPoint(x + dx, y),
        new mxPoint(x + w - dx, y),
        new mxPoint(x + w, cy),
        new mxPoint(x + w - dx, y + h),
        new mxPoint(x + dx, y + h),
        new mxPoint(x, cy),
        new mxPoint(x + dx, y),
      ];
    }

    var p1 = new mxPoint(cx, cy);

    if (orthogonal) {
      if (next.x < x || next.x > x + w) {
        p1.y = next.y;
      } else {
        p1.x = next.x;
      }
    }

    return mxUtils.getPerimeterPoint(points, p1, next);
  };

  mxStyleRegistry.putValue('hexagonPerimeter2', mxPerimeter.HexagonPerimeter2);

  // Provided Interface Shape (aka Lollipop)
  function LollipopShape() {
    mxShape.call(this);
  }
  mxUtils.extend(LollipopShape, mxShape);
  LollipopShape.prototype.size = 10;
  LollipopShape.prototype.paintBackground = function (c, x, y, w, h) {
    var sz = parseFloat(mxUtils.getValue(this.style, 'size', this.size));
    c.translate(x, y);

    c.ellipse((w - sz) / 2, 0, sz, sz);
    c.fillAndStroke();

    c.begin();
    c.moveTo(w / 2, sz);
    c.lineTo(w / 2, h);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('lollipop', LollipopShape);

  // Required Interface Shape
  function RequiresShape() {
    mxShape.call(this);
  }
  mxUtils.extend(RequiresShape, mxShape);
  RequiresShape.prototype.size = 10;
  RequiresShape.prototype.inset = 2;
  RequiresShape.prototype.paintBackground = function (c, x, y, w, h) {
    var sz = parseFloat(mxUtils.getValue(this.style, 'size', this.size));
    var inset = parseFloat(mxUtils.getValue(this.style, 'inset', this.inset)) + this.strokewidth;
    c.translate(x, y);

    c.begin();
    c.moveTo(w / 2, sz + inset);
    c.lineTo(w / 2, h);
    c.end();
    c.stroke();

    c.begin();
    c.moveTo((w - sz) / 2 - inset, sz / 2);
    c.quadTo((w - sz) / 2 - inset, sz + inset, w / 2, sz + inset);
    c.quadTo((w + sz) / 2 + inset, sz + inset, (w + sz) / 2 + inset, sz / 2);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('requires', RequiresShape);

  // Required Interface Shape
  function RequiredInterfaceShape() {
    mxShape.call(this);
  }
  mxUtils.extend(RequiredInterfaceShape, mxShape);

  RequiredInterfaceShape.prototype.paintBackground = function (c, x, y, w, h) {
    c.translate(x, y);

    c.begin();
    c.moveTo(0, 0);
    c.quadTo(w, 0, w, h / 2);
    c.quadTo(w, h, 0, h);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('requiredInterface', RequiredInterfaceShape);

  // Provided and Required Interface Shape
  function ProvidedRequiredInterfaceShape() {
    mxShape.call(this);
  }
  mxUtils.extend(ProvidedRequiredInterfaceShape, mxShape);
  ProvidedRequiredInterfaceShape.prototype.inset = 2;
  ProvidedRequiredInterfaceShape.prototype.paintBackground = function (c, x, y, w, h) {
    var inset = parseFloat(mxUtils.getValue(this.style, 'inset', this.inset)) + this.strokewidth;
    c.translate(x, y);

    c.ellipse(0, inset, w - 2 * inset, h - 2 * inset);
    c.fillAndStroke();

    c.begin();
    c.moveTo(w / 2, 0);
    c.quadTo(w, 0, w, h / 2);
    c.quadTo(w, h, w / 2, h);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('providedRequiredInterface', ProvidedRequiredInterfaceShape);

  // Module shape
  function ModuleShape() {
    mxCylinder.call(this);
  }
  mxUtils.extend(ModuleShape, mxCylinder);
  ModuleShape.prototype.jettyWidth = 20;
  ModuleShape.prototype.jettyHeight = 10;
  ModuleShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
    var dx = parseFloat(mxUtils.getValue(this.style, 'jettyWidth', this.jettyWidth));
    var dy = parseFloat(mxUtils.getValue(this.style, 'jettyHeight', this.jettyHeight));
    var x0 = dx / 2;
    var x1 = x0 + dx / 2;
    var y0 = Math.min(dy, h - dy);
    var y1 = Math.min(y0 + 2 * dy, h - dy);

    if (isForeground) {
      path.moveTo(x0, y0);
      path.lineTo(x1, y0);
      path.lineTo(x1, y0 + dy);
      path.lineTo(x0, y0 + dy);
      path.moveTo(x0, y1);
      path.lineTo(x1, y1);
      path.lineTo(x1, y1 + dy);
      path.lineTo(x0, y1 + dy);
      path.end();
    } else {
      path.moveTo(x0, 0);
      path.lineTo(w, 0);
      path.lineTo(w, h);
      path.lineTo(x0, h);
      path.lineTo(x0, y1 + dy);
      path.lineTo(0, y1 + dy);
      path.lineTo(0, y1);
      path.lineTo(x0, y1);
      path.lineTo(x0, y0 + dy);
      path.lineTo(0, y0 + dy);
      path.lineTo(0, y0);
      path.lineTo(x0, y0);
      path.close();
      path.end();
    }
  };

  mxCellRenderer.registerShape('module', ModuleShape);

  // Component shape
  function ComponentShape() {
    mxCylinder.call(this);
  }
  mxUtils.extend(ComponentShape, mxCylinder);
  ComponentShape.prototype.jettyWidth = 32;
  ComponentShape.prototype.jettyHeight = 12;
  ComponentShape.prototype.redrawPath = function (path, x, y, w, h, isForeground) {
    var dx = parseFloat(mxUtils.getValue(this.style, 'jettyWidth', this.jettyWidth));
    var dy = parseFloat(mxUtils.getValue(this.style, 'jettyHeight', this.jettyHeight));
    var x0 = dx / 2;
    var x1 = x0 + dx / 2;
    var y0 = 0.3 * h - dy / 2;
    var y1 = 0.7 * h - dy / 2;

    if (isForeground) {
      path.moveTo(x0, y0);
      path.lineTo(x1, y0);
      path.lineTo(x1, y0 + dy);
      path.lineTo(x0, y0 + dy);
      path.moveTo(x0, y1);
      path.lineTo(x1, y1);
      path.lineTo(x1, y1 + dy);
      path.lineTo(x0, y1 + dy);
      path.end();
    } else {
      path.moveTo(x0, 0);
      path.lineTo(w, 0);
      path.lineTo(w, h);
      path.lineTo(x0, h);
      path.lineTo(x0, y1 + dy);
      path.lineTo(0, y1 + dy);
      path.lineTo(0, y1);
      path.lineTo(x0, y1);
      path.lineTo(x0, y0 + dy);
      path.lineTo(0, y0 + dy);
      path.lineTo(0, y0);
      path.lineTo(x0, y0);
      path.close();
      path.end();
    }
  };

  mxCellRenderer.registerShape('component', ComponentShape);

  // Associative entity derived from rectangle shape
  function AssociativeEntity() {
    mxRectangleShape.call(this);
  }
  mxUtils.extend(AssociativeEntity, mxRectangleShape);
  AssociativeEntity.prototype.paintForeground = function (c, x, y, w, h) {
    var hw = w / 2;
    var hh = h / 2;

    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    c.begin();
    this.addPoints(
      c,
      [new mxPoint(x + hw, y), new mxPoint(x + w, y + hh), new mxPoint(x + hw, y + h), new mxPoint(x, y + hh)],
      this.isRounded,
      arcSize,
      true
    );
    c.stroke();

    mxRectangleShape.prototype.paintForeground.apply(this, arguments);
  };

  mxCellRenderer.registerShape('associativeEntity', AssociativeEntity);

  // State Shapes derives from double ellipse
  function StateShape() {
    mxDoubleEllipse.call(this);
  }
  mxUtils.extend(StateShape, mxDoubleEllipse);
  StateShape.prototype.outerStroke = true;
  StateShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    var inset = Math.min(4, Math.min(w / 5, h / 5));

    if (w > 0 && h > 0) {
      c.ellipse(x + inset, y + inset, w - 2 * inset, h - 2 * inset);
      c.fillAndStroke();
    }

    c.setShadow(false);

    if (this.outerStroke) {
      c.ellipse(x, y, w, h);
      c.stroke();
    }
  };

  mxCellRenderer.registerShape('endState', StateShape);

  function StartStateShape() {
    StateShape.call(this);
  }
  mxUtils.extend(StartStateShape, StateShape);
  StartStateShape.prototype.outerStroke = false;

  mxCellRenderer.registerShape('startState', StartStateShape);

  // Link shape
  function LinkShape() {
    mxArrowConnector.call(this);
    this.spacing = 0;
  }
  mxUtils.extend(LinkShape, mxArrowConnector);
  LinkShape.prototype.defaultWidth = 4;

  LinkShape.prototype.isOpenEnded = function () {
    return true;
  };

  LinkShape.prototype.getEdgeWidth = function () {
    return mxUtils.getNumber(this.style, 'width', this.defaultWidth) + Math.max(0, this.strokewidth - 1);
  };

  LinkShape.prototype.isArrowRounded = function () {
    return this.isRounded;
  };

  // Registers the link shape
  mxCellRenderer.registerShape('link', LinkShape);

  // Generic arrow
  function FlexArrowShape() {
    mxArrowConnector.call(this);
    this.spacing = 0;
  }
  mxUtils.extend(FlexArrowShape, mxArrowConnector);
  FlexArrowShape.prototype.defaultWidth = 10;
  FlexArrowShape.prototype.defaultArrowWidth = 20;

  FlexArrowShape.prototype.getStartArrowWidth = function () {
    return this.getEdgeWidth() + mxUtils.getNumber(this.style, 'startWidth', this.defaultArrowWidth);
  };

  FlexArrowShape.prototype.getEndArrowWidth = function () {
    return this.getEdgeWidth() + mxUtils.getNumber(this.style, 'endWidth', this.defaultArrowWidth);
  };

  FlexArrowShape.prototype.getEdgeWidth = function () {
    return mxUtils.getNumber(this.style, 'width', this.defaultWidth) + Math.max(0, this.strokewidth - 1);
  };

  // Registers the link shape
  mxCellRenderer.registerShape('flexArrow', FlexArrowShape);

  // Manual Input shape
  function ManualInputShape() {
    mxActor.call(this);
  }
  mxUtils.extend(ManualInputShape, mxActor);
  ManualInputShape.prototype.size = 30;
  ManualInputShape.prototype.isRoundable = function () {
    return true;
  };
  ManualInputShape.prototype.redrawPath = function (c, x, y, w, h) {
    var s = Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [new mxPoint(0, h), new mxPoint(0, s), new mxPoint(w, 0), new mxPoint(w, h)],
      this.isRounded,
      arcSize,
      true
    );
    c.end();
  };

  mxCellRenderer.registerShape('manualInput', ManualInputShape);

  // Internal storage
  function InternalStorageShape() {
    mxRectangleShape.call(this);
  }
  mxUtils.extend(InternalStorageShape, mxRectangleShape);
  InternalStorageShape.prototype.dx = 20;
  InternalStorageShape.prototype.dy = 20;
  InternalStorageShape.prototype.isHtmlAllowed = function () {
    return false;
  };
  InternalStorageShape.prototype.paintForeground = function (c, x, y, w, h) {
    mxRectangleShape.prototype.paintForeground.apply(this, arguments);
    var inset = 0;

    if (this.isRounded) {
      var f =
        mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
      inset = Math.max(inset, Math.min(w * f, h * f));
    }

    var dx = Math.max(inset, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'dx', this.dx))));
    var dy = Math.max(inset, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'dy', this.dy))));

    c.begin();
    c.moveTo(x, y + dy);
    c.lineTo(x + w, y + dy);
    c.end();
    c.stroke();

    c.begin();
    c.moveTo(x + dx, y);
    c.lineTo(x + dx, y + h);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('internalStorage', InternalStorageShape);

  // Internal storage
  function CornerShape() {
    mxActor.call(this);
  }
  mxUtils.extend(CornerShape, mxActor);
  CornerShape.prototype.dx = 20;
  CornerShape.prototype.dy = 20;

  // Corner
  CornerShape.prototype.redrawPath = function (c, x, y, w, h) {
    var dx = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'dx', this.dx))));
    var dy = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'dy', this.dy))));

    var s = Math.min(w / 2, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [
        new mxPoint(0, 0),
        new mxPoint(w, 0),
        new mxPoint(w, dy),
        new mxPoint(dx, dy),
        new mxPoint(dx, h),
        new mxPoint(0, h),
      ],
      this.isRounded,
      arcSize,
      true
    );
    c.end();
  };

  mxCellRenderer.registerShape('corner', CornerShape);

  // Crossbar shape
  function CrossbarShape() {
    mxActor.call(this);
  }
  mxUtils.extend(CrossbarShape, mxActor);

  CrossbarShape.prototype.redrawPath = function (c, x, y, w, h) {
    c.moveTo(0, 0);
    c.lineTo(0, h);
    c.end();

    c.moveTo(w, 0);
    c.lineTo(w, h);
    c.end();

    c.moveTo(0, h / 2);
    c.lineTo(w, h / 2);
    c.end();
  };

  mxCellRenderer.registerShape('crossbar', CrossbarShape);

  // Internal storage
  function TeeShape() {
    mxActor.call(this);
  }
  mxUtils.extend(TeeShape, mxActor);
  TeeShape.prototype.dx = 20;
  TeeShape.prototype.dy = 20;

  // Corner
  TeeShape.prototype.redrawPath = function (c, x, y, w, h) {
    var dx = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'dx', this.dx))));
    var dy = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'dy', this.dy))));
    var w2 = Math.abs(w - dx) / 2;

    var s = Math.min(w / 2, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [
        new mxPoint(0, 0),
        new mxPoint(w, 0),
        new mxPoint(w, dy),
        new mxPoint((w + dx) / 2, dy),
        new mxPoint((w + dx) / 2, h),
        new mxPoint((w - dx) / 2, h),
        new mxPoint((w - dx) / 2, dy),
        new mxPoint(0, dy),
      ],
      this.isRounded,
      arcSize,
      true
    );
    c.end();
  };

  mxCellRenderer.registerShape('tee', TeeShape);

  // Arrow
  function SingleArrowShape() {
    mxActor.call(this);
  }
  mxUtils.extend(SingleArrowShape, mxActor);
  SingleArrowShape.prototype.arrowWidth = 0.3;
  SingleArrowShape.prototype.arrowSize = 0.2;
  SingleArrowShape.prototype.redrawPath = function (c, x, y, w, h) {
    var aw = h * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowWidth', this.arrowWidth))));
    var as = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowSize', this.arrowSize))));
    var at = (h - aw) / 2;
    var ab = at + aw;

    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [
        new mxPoint(0, at),
        new mxPoint(w - as, at),
        new mxPoint(w - as, 0),
        new mxPoint(w, h / 2),
        new mxPoint(w - as, h),
        new mxPoint(w - as, ab),
        new mxPoint(0, ab),
      ],
      this.isRounded,
      arcSize,
      true
    );
    c.end();
  };

  mxCellRenderer.registerShape('singleArrow', SingleArrowShape);

  // Arrow
  function DoubleArrowShape() {
    mxActor.call(this);
  }
  mxUtils.extend(DoubleArrowShape, mxActor);
  DoubleArrowShape.prototype.redrawPath = function (c, x, y, w, h) {
    var aw =
      h *
      Math.max(
        0,
        Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowWidth', SingleArrowShape.prototype.arrowWidth)))
      );
    var as =
      w *
      Math.max(
        0,
        Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowSize', SingleArrowShape.prototype.arrowSize)))
      );
    var at = (h - aw) / 2;
    var ab = at + aw;

    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [
        new mxPoint(0, h / 2),
        new mxPoint(as, 0),
        new mxPoint(as, at),
        new mxPoint(w - as, at),
        new mxPoint(w - as, 0),
        new mxPoint(w, h / 2),
        new mxPoint(w - as, h),
        new mxPoint(w - as, ab),
        new mxPoint(as, ab),
        new mxPoint(as, h),
      ],
      this.isRounded,
      arcSize,
      true
    );
    c.end();
  };

  mxCellRenderer.registerShape('doubleArrow', DoubleArrowShape);

  // Data storage
  function DataStorageShape() {
    mxActor.call(this);
  }
  mxUtils.extend(DataStorageShape, mxActor);
  DataStorageShape.prototype.size = 0.1;
  DataStorageShape.prototype.fixedSize = 20;
  DataStorageShape.prototype.redrawPath = function (c, x, y, w, h) {
    var fixed = mxUtils.getValue(this.style, 'fixedSize', '0') != '0';
    var s = fixed
      ? Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'size', this.fixedSize))))
      : w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));

    c.moveTo(s, 0);
    c.lineTo(w, 0);
    c.quadTo(w - s * 2, h / 2, w, h);
    c.lineTo(s, h);
    c.quadTo(s - s * 2, h / 2, s, 0);
    c.close();
    c.end();
  };

  mxCellRenderer.registerShape('dataStorage', DataStorageShape);

  // Or
  function OrShape() {
    mxActor.call(this);
  }
  mxUtils.extend(OrShape, mxActor);
  OrShape.prototype.redrawPath = function (c, x, y, w, h) {
    c.moveTo(0, 0);
    c.quadTo(w, 0, w, h / 2);
    c.quadTo(w, h, 0, h);
    c.close();
    c.end();
  };

  mxCellRenderer.registerShape('or', OrShape);

  // Xor
  function XorShape() {
    mxActor.call(this);
  }
  mxUtils.extend(XorShape, mxActor);
  XorShape.prototype.redrawPath = function (c, x, y, w, h) {
    c.moveTo(0, 0);
    c.quadTo(w, 0, w, h / 2);
    c.quadTo(w, h, 0, h);
    c.quadTo(w / 2, h / 2, 0, 0);
    c.close();
    c.end();
  };

  mxCellRenderer.registerShape('xor', XorShape);

  // Loop limit
  function LoopLimitShape() {
    mxActor.call(this);
  }
  mxUtils.extend(LoopLimitShape, mxActor);
  LoopLimitShape.prototype.size = 20;
  LoopLimitShape.prototype.isRoundable = function () {
    return true;
  };
  LoopLimitShape.prototype.redrawPath = function (c, x, y, w, h) {
    var s = Math.min(w / 2, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [
        new mxPoint(s, 0),
        new mxPoint(w - s, 0),
        new mxPoint(w, s * 0.8),
        new mxPoint(w, h),
        new mxPoint(0, h),
        new mxPoint(0, s * 0.8),
      ],
      this.isRounded,
      arcSize,
      true
    );
    c.end();
  };

  mxCellRenderer.registerShape('loopLimit', LoopLimitShape);

  // Off page connector
  function OffPageConnectorShape() {
    mxActor.call(this);
  }
  mxUtils.extend(OffPageConnectorShape, mxActor);
  OffPageConnectorShape.prototype.size = 3 / 8;
  OffPageConnectorShape.prototype.isRoundable = function () {
    return true;
  };
  OffPageConnectorShape.prototype.redrawPath = function (c, x, y, w, h) {
    var s = h * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    this.addPoints(
      c,
      [new mxPoint(0, 0), new mxPoint(w, 0), new mxPoint(w, h - s), new mxPoint(w / 2, h), new mxPoint(0, h - s)],
      this.isRounded,
      arcSize,
      true
    );
    c.end();
  };

  mxCellRenderer.registerShape('offPageConnector', OffPageConnectorShape);

  // Internal storage
  function TapeDataShape() {
    mxEllipse.call(this);
  }
  mxUtils.extend(TapeDataShape, mxEllipse);
  TapeDataShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    mxEllipse.prototype.paintVertexShape.apply(this, arguments);

    c.begin();
    c.moveTo(x + w / 2, y + h);
    c.lineTo(x + w, y + h);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('tapeData', TapeDataShape);

  // OrEllipseShape
  function OrEllipseShape() {
    mxEllipse.call(this);
  }
  mxUtils.extend(OrEllipseShape, mxEllipse);
  OrEllipseShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    mxEllipse.prototype.paintVertexShape.apply(this, arguments);

    c.setShadow(false);
    c.begin();
    c.moveTo(x, y + h / 2);
    c.lineTo(x + w, y + h / 2);
    c.end();
    c.stroke();

    c.begin();
    c.moveTo(x + w / 2, y);
    c.lineTo(x + w / 2, y + h);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('orEllipse', OrEllipseShape);

  // SumEllipseShape
  function SumEllipseShape() {
    mxEllipse.call(this);
  }
  mxUtils.extend(SumEllipseShape, mxEllipse);
  SumEllipseShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    mxEllipse.prototype.paintVertexShape.apply(this, arguments);
    var s2 = 0.145;

    c.setShadow(false);
    c.begin();
    c.moveTo(x + w * s2, y + h * s2);
    c.lineTo(x + w * (1 - s2), y + h * (1 - s2));
    c.end();
    c.stroke();

    c.begin();
    c.moveTo(x + w * (1 - s2), y + h * s2);
    c.lineTo(x + w * s2, y + h * (1 - s2));
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('sumEllipse', SumEllipseShape);

  // SortShape
  function SortShape() {
    mxRhombus.call(this);
  }
  mxUtils.extend(SortShape, mxRhombus);
  SortShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    mxRhombus.prototype.paintVertexShape.apply(this, arguments);

    c.setShadow(false);
    c.begin();
    c.moveTo(x, y + h / 2);
    c.lineTo(x + w, y + h / 2);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('sortShape', SortShape);

  // CollateShape
  function CollateShape() {
    mxEllipse.call(this);
  }
  mxUtils.extend(CollateShape, mxEllipse);
  CollateShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    c.begin();
    c.moveTo(x, y);
    c.lineTo(x + w, y);
    c.lineTo(x + w / 2, y + h / 2);
    c.close();
    c.fillAndStroke();

    c.begin();
    c.moveTo(x, y + h);
    c.lineTo(x + w, y + h);
    c.lineTo(x + w / 2, y + h / 2);
    c.close();
    c.fillAndStroke();
  };

  mxCellRenderer.registerShape('collate', CollateShape);

  // DimensionShape
  function DimensionShape() {
    mxEllipse.call(this);
  }
  mxUtils.extend(DimensionShape, mxEllipse);
  DimensionShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    // Arrow size
    var al = 10;
    var cy = y + h - al / 2;

    c.begin();
    c.moveTo(x, y);
    c.lineTo(x, y + h);
    c.moveTo(x, cy);
    c.lineTo(x + al, cy - al / 2);
    c.moveTo(x, cy);
    c.lineTo(x + al, cy + al / 2);
    c.moveTo(x, cy);
    c.lineTo(x + w, cy);

    // Opposite side
    c.moveTo(x + w, y);
    c.lineTo(x + w, y + h);
    c.moveTo(x + w, cy);
    c.lineTo(x + w - al, cy - al / 2);
    c.moveTo(x + w, cy);
    c.lineTo(x + w - al, cy + al / 2);
    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('dimension', DimensionShape);

  // PartialRectangleShape
  function PartialRectangleShape() {
    mxEllipse.call(this);
  }
  mxUtils.extend(PartialRectangleShape, mxEllipse);
  PartialRectangleShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    if (!this.outline) {
      c.setStrokeColor(null);
    }

    if (this.style != null) {
      var pointerEvents = c.pointerEvents;
      var events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';

      if (!events && (this.fill == null || this.fill == mxConstants.NONE)) {
        c.pointerEvents = false;
      }

      c.rect(x, y, w, h);
      c.fill();

      c.pointerEvents = pointerEvents;
      c.setStrokeColor(this.stroke);
      c.begin();
      c.moveTo(x, y);

      if (this.outline || mxUtils.getValue(this.style, 'top', '1') == '1') {
        c.lineTo(x + w, y);
      } else {
        c.moveTo(x + w, y);
      }

      if (this.outline || mxUtils.getValue(this.style, 'right', '1') == '1') {
        c.lineTo(x + w, y + h);
      } else {
        c.moveTo(x + w, y + h);
      }

      if (this.outline || mxUtils.getValue(this.style, 'bottom', '1') == '1') {
        c.lineTo(x, y + h);
      } else {
        c.moveTo(x, y + h);
      }

      if (this.outline || mxUtils.getValue(this.style, 'left', '1') == '1') {
        c.lineTo(x, y);
      }

      c.end();
      c.stroke();
    }
  };

  mxCellRenderer.registerShape('partialRectangle', PartialRectangleShape);

  // LineEllipseShape
  function LineEllipseShape() {
    mxEllipse.call(this);
  }
  mxUtils.extend(LineEllipseShape, mxEllipse);
  LineEllipseShape.prototype.paintVertexShape = function (c, x, y, w, h) {
    mxEllipse.prototype.paintVertexShape.apply(this, arguments);

    c.setShadow(false);
    c.begin();

    if (mxUtils.getValue(this.style, 'line') == 'vertical') {
      c.moveTo(x + w / 2, y);
      c.lineTo(x + w / 2, y + h);
    } else {
      c.moveTo(x, y + h / 2);
      c.lineTo(x + w, y + h / 2);
    }

    c.end();
    c.stroke();
  };

  mxCellRenderer.registerShape('lineEllipse', LineEllipseShape);

  // Delay
  function DelayShape() {
    mxActor.call(this);
  }
  mxUtils.extend(DelayShape, mxActor);
  DelayShape.prototype.redrawPath = function (c, x, y, w, h) {
    var dx = Math.min(w, h / 2);
    c.moveTo(0, 0);
    c.lineTo(w - dx, 0);
    c.quadTo(w, 0, w, h / 2);
    c.quadTo(w, h, w - dx, h);
    c.lineTo(0, h);
    c.close();
    c.end();
  };

  mxCellRenderer.registerShape('delay', DelayShape);

  // Cross Shape
  function CrossShape() {
    mxActor.call(this);
  }
  mxUtils.extend(CrossShape, mxActor);
  CrossShape.prototype.size = 0.2;
  CrossShape.prototype.redrawPath = function (c, x, y, w, h) {
    var m = Math.min(h, w);
    var size = Math.max(0, Math.min(m, m * parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var t = (h - size) / 2;
    var b = t + size;
    var l = (w - size) / 2;
    var r = l + size;

    c.moveTo(0, t);
    c.lineTo(l, t);
    c.lineTo(l, 0);
    c.lineTo(r, 0);
    c.lineTo(r, t);
    c.lineTo(w, t);
    c.lineTo(w, b);
    c.lineTo(r, b);
    c.lineTo(r, h);
    c.lineTo(l, h);
    c.lineTo(l, b);
    c.lineTo(0, b);
    c.close();
    c.end();
  };

  mxCellRenderer.registerShape('cross', CrossShape);

  // Display
  function DisplayShape() {
    mxActor.call(this);
  }
  mxUtils.extend(DisplayShape, mxActor);
  DisplayShape.prototype.size = 0.25;
  DisplayShape.prototype.redrawPath = function (c, x, y, w, h) {
    var dx = Math.min(w, h / 2);
    var s = Math.min(w - dx, Math.max(0, parseFloat(mxUtils.getValue(this.style, 'size', this.size))) * w);

    c.moveTo(0, h / 2);
    c.lineTo(s, 0);
    c.lineTo(w - dx, 0);
    c.quadTo(w, 0, w, h / 2);
    c.quadTo(w, h, w - dx, h);
    c.lineTo(s, h);
    c.close();
    c.end();
  };

  mxCellRenderer.registerShape('display', DisplayShape);

  // FilledEdge shape
  function FilledEdge() {
    mxConnector.call(this);
  }
  mxUtils.extend(FilledEdge, mxConnector);

  FilledEdge.prototype.origPaintEdgeShape = FilledEdge.prototype.paintEdgeShape;
  FilledEdge.prototype.paintEdgeShape = function (c, pts, rounded) {
    // Markers modify incoming points array
    var temp = [];

    for (var i = 0; i < pts.length; i++) {
      temp.push(mxUtils.clone(pts[i]));
    }

    // paintEdgeShape resets dashed to false
    var dashed = c.state.dashed;
    var fixDash = c.state.fixDash;
    FilledEdge.prototype.origPaintEdgeShape.apply(this, [c, temp, rounded]);

    if (c.state.strokeWidth >= 3) {
      var fillClr = mxUtils.getValue(this.style, 'fillColor', null);

      if (fillClr != null) {
        c.setStrokeColor(fillClr);
        c.setStrokeWidth(c.state.strokeWidth - 2);
        c.setDashed(dashed, fixDash);

        FilledEdge.prototype.origPaintEdgeShape.apply(this, [c, pts, rounded]);
      }
    }
  };

  // Registers the link shape
  mxCellRenderer.registerShape('filledEdge', FilledEdge);

  // Implements custom colors for shapes
  if (typeof StyleFormatPanel !== 'undefined') {
    (function () {
      var styleFormatPanelGetCustomColors = StyleFormatPanel.prototype.getCustomColors;

      StyleFormatPanel.prototype.getCustomColors = function () {
        var ss = this.format.getSelectionState();
        var result = styleFormatPanelGetCustomColors.apply(this, arguments);

        if (ss.style.shape == 'umlFrame') {
          result.push({ title: mxResources.get('laneColor'), key: 'swimlaneFillColor', defaultValue: '#ffffff' });
        }

        return result;
      };
    })();
  }

  // Registers and defines the custom marker
  mxMarker.addMarker('dash', function (c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    var nx = unitX * (size + sw + 1);
    var ny = unitY * (size + sw + 1);

    return function () {
      c.begin();
      c.moveTo(pe.x - nx / 2 - ny / 2, pe.y - ny / 2 + nx / 2);
      c.lineTo(pe.x + ny / 2 - (3 * nx) / 2, pe.y - (3 * ny) / 2 - nx / 2);
      c.stroke();
    };
  });

  // Registers and defines the custom marker
  mxMarker.addMarker('box', function (c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    var nx = unitX * (size + sw + 1);
    var ny = unitY * (size + sw + 1);
    var px = pe.x + nx / 2;
    var py = pe.y + ny / 2;

    pe.x -= nx;
    pe.y -= ny;

    return function () {
      c.begin();
      c.moveTo(px - nx / 2 - ny / 2, py - ny / 2 + nx / 2);
      c.lineTo(px - nx / 2 + ny / 2, py - ny / 2 - nx / 2);
      c.lineTo(px + ny / 2 - (3 * nx) / 2, py - (3 * ny) / 2 - nx / 2);
      c.lineTo(px - ny / 2 - (3 * nx) / 2, py - (3 * ny) / 2 + nx / 2);
      c.close();

      if (filled) {
        c.fillAndStroke();
      } else {
        c.stroke();
      }
    };
  });

  // Registers and defines the custom marker
  mxMarker.addMarker('cross', function (c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    var nx = unitX * (size + sw + 1);
    var ny = unitY * (size + sw + 1);

    return function () {
      c.begin();
      c.moveTo(pe.x - nx / 2 - ny / 2, pe.y - ny / 2 + nx / 2);
      c.lineTo(pe.x + ny / 2 - (3 * nx) / 2, pe.y - (3 * ny) / 2 - nx / 2);
      c.moveTo(pe.x - nx / 2 + ny / 2, pe.y - ny / 2 - nx / 2);
      c.lineTo(pe.x - ny / 2 - (3 * nx) / 2, pe.y - (3 * ny) / 2 + nx / 2);
      c.stroke();
    };
  });

  function circleMarker(c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    var a = size / 2;
    var size = size + sw;

    var pt = pe.clone();

    pe.x -= unitX * (2 * size + sw);
    pe.y -= unitY * (2 * size + sw);

    unitX = unitX * (size + sw);
    unitY = unitY * (size + sw);

    return function () {
      c.ellipse(pt.x - unitX - size, pt.y - unitY - size, 2 * size, 2 * size);

      if (filled) {
        c.fillAndStroke();
      } else {
        c.stroke();
      }
    };
  }

  mxMarker.addMarker('circle', circleMarker);
  mxMarker.addMarker('circlePlus', function (c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    var pt = pe.clone();
    var fn = circleMarker.apply(this, arguments);
    var nx = unitX * (size + 2 * sw); // (size + sw + 1);
    var ny = unitY * (size + 2 * sw); //(size + sw + 1);

    return function () {
      fn.apply(this, arguments);

      c.begin();
      c.moveTo(pt.x - unitX * sw, pt.y - unitY * sw);
      c.lineTo(pt.x - 2 * nx + unitX * sw, pt.y - 2 * ny + unitY * sw);
      c.moveTo(pt.x - nx - ny + unitY * sw, pt.y - ny + nx - unitX * sw);
      c.lineTo(pt.x + ny - nx - unitY * sw, pt.y - ny - nx + unitX * sw);
      c.stroke();
    };
  });

  // Registers and defines the custom marker
  mxMarker.addMarker('halfCircle', function (c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    var nx = unitX * (size + sw + 1);
    var ny = unitY * (size + sw + 1);
    var pt = pe.clone();

    pe.x -= nx;
    pe.y -= ny;

    return function () {
      c.begin();
      c.moveTo(pt.x - ny, pt.y + nx);
      c.quadTo(pe.x - ny, pe.y + nx, pe.x, pe.y);
      c.quadTo(pe.x + ny, pe.y - nx, pt.x + ny, pt.y - nx);
      c.stroke();
    };
  });

  mxMarker.addMarker('async', function (c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    // The angle of the forward facing arrow sides against the x axis is
    // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
    // only half the strokewidth is processed ).
    var endOffsetX = unitX * sw * 1.118;
    var endOffsetY = unitY * sw * 1.118;

    unitX = unitX * (size + sw);
    unitY = unitY * (size + sw);

    var pt = pe.clone();
    pt.x -= endOffsetX;
    pt.y -= endOffsetY;

    var f = 1;
    pe.x += -unitX * f - endOffsetX;
    pe.y += -unitY * f - endOffsetY;

    return function () {
      c.begin();
      c.moveTo(pt.x, pt.y);

      if (source) {
        c.lineTo(pt.x - unitX - unitY / 2, pt.y - unitY + unitX / 2);
      } else {
        c.lineTo(pt.x + unitY / 2 - unitX, pt.y - unitY - unitX / 2);
      }

      c.lineTo(pt.x - unitX, pt.y - unitY);
      c.close();

      if (filled) {
        c.fillAndStroke();
      } else {
        c.stroke();
      }
    };
  });

  function createOpenAsyncArrow(widthFactor) {
    widthFactor = widthFactor != null ? widthFactor : 2;

    return function (c, shape, type, pe, unitX, unitY, size, source, sw, filled) {
      unitX = unitX * (size + sw);
      unitY = unitY * (size + sw);

      var pt = pe.clone();

      return function () {
        c.begin();
        c.moveTo(pt.x, pt.y);

        if (source) {
          c.lineTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);
        } else {
          c.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
        }

        c.stroke();
      };
    };
  }

  mxMarker.addMarker('openAsync', createOpenAsyncArrow(2));

  function arrow(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled) {
    // The angle of the forward facing arrow sides against the x axis is
    // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
    // only half the strokewidth is processed ).
    var endOffsetX = unitX * sw * 1.118;
    var endOffsetY = unitY * sw * 1.118;

    unitX = unitX * (size + sw);
    unitY = unitY * (size + sw);

    var pt = pe.clone();
    pt.x -= endOffsetX;
    pt.y -= endOffsetY;

    var f = type != mxConstants.ARROW_CLASSIC && type != mxConstants.ARROW_CLASSIC_THIN ? 1 : 3 / 4;
    pe.x += -unitX * f - endOffsetX;
    pe.y += -unitY * f - endOffsetY;

    return function () {
      canvas.begin();
      canvas.moveTo(pt.x, pt.y);
      canvas.lineTo(pt.x - unitX - unitY / widthFactor, pt.y - unitY + unitX / widthFactor);

      if (type == mxConstants.ARROW_CLASSIC || type == mxConstants.ARROW_CLASSIC_THIN) {
        canvas.lineTo(pt.x - (unitX * 3) / 4, pt.y - (unitY * 3) / 4);
      }

      canvas.lineTo(pt.x + unitY / widthFactor - unitX, pt.y - unitY - unitX / widthFactor);
      canvas.close();

      if (filled) {
        canvas.fillAndStroke();
      } else {
        canvas.stroke();
      }
    };
  }

  // Handlers are only added if mxVertexHandler is defined (ie. not in embedded graph)
  if (typeof mxVertexHandler !== 'undefined') {
    function createHandle(state, keys, getPositionFn, setPositionFn, ignoreGrid, redrawEdges, executeFn) {
      var handle = new mxHandle(state, null, mxVertexHandler.prototype.secondaryHandleImage);

      handle.execute = function (me) {
        for (var i = 0; i < keys.length; i++) {
          this.copyStyle(keys[i]);
        }

        if (executeFn) {
          executeFn(me);
        }
      };

      handle.getPosition = getPositionFn;
      handle.setPosition = setPositionFn;
      handle.ignoreGrid = ignoreGrid != null ? ignoreGrid : true;

      // Overridden to update connected edges
      if (redrawEdges) {
        var positionChanged = handle.positionChanged;

        handle.positionChanged = function () {
          positionChanged.apply(this, arguments);

          // Redraws connected edges TODO: Include child edges
          state.view.invalidate(this.state.cell);
          state.view.validate();
        };
      }

      return handle;
    }

    function createArcHandle(state, yOffset) {
      return createHandle(
        state,
        [mxConstants.STYLE_ARCSIZE],
        function (bounds) {
          var tmp = yOffset != null ? yOffset : bounds.height / 8;

          if (mxUtils.getValue(state.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') {
            var arcSize = mxUtils.getValue(state.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;

            return new mxPoint(bounds.x + bounds.width - Math.min(bounds.width / 2, arcSize), bounds.y + tmp);
          } else {
            var arcSize =
              Math.max(
                0,
                parseFloat(
                  mxUtils.getValue(state.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100)
                )
              ) / 100;

            return new mxPoint(
              bounds.x +
                bounds.width -
                Math.min(
                  Math.max(bounds.width / 2, bounds.height / 2),
                  Math.min(bounds.width, bounds.height) * arcSize
                ),
              bounds.y + tmp
            );
          }
        },
        function (bounds, pt, me) {
          if (mxUtils.getValue(state.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') {
            this.state.style[mxConstants.STYLE_ARCSIZE] = Math.round(
              Math.max(0, Math.min(bounds.width, (bounds.x + bounds.width - pt.x) * 2))
            );
          } else {
            var f = Math.min(
              50,
              Math.max(0, ((bounds.width - pt.x + bounds.x) * 100) / Math.min(bounds.width, bounds.height))
            );
            this.state.style[mxConstants.STYLE_ARCSIZE] = Math.round(f);
          }
        }
      );
    }

    function createArcHandleFunction() {
      return function (state) {
        var handles = [];

        if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
          handles.push(createArcHandle(state));
        }

        return handles;
      };
    }

    function createTrapezoidHandleFunction(max, defaultValue, fixedDefaultValue) {
      max = max != null ? max : 0.5;

      return function (state) {
        var handles = [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var fixed =
                fixedDefaultValue != null ? mxUtils.getValue(this.state.style, 'fixedSize', '0') != '0' : null;
              var size = Math.max(
                0,
                parseFloat(mxUtils.getValue(this.state.style, 'size', fixed ? fixedDefaultValue : defaultValue))
              );

              return new mxPoint(
                bounds.x + Math.min(bounds.width * 0.75 * max, size * (fixed ? 0.75 : bounds.width * 0.75)),
                bounds.y + bounds.height / 4
              );
            },
            function (bounds, pt) {
              var fixed =
                fixedDefaultValue != null ? mxUtils.getValue(this.state.style, 'fixedSize', '0') != '0' : null;
              var size = fixed
                ? pt.x - bounds.x
                : Math.max(0, Math.min(max, ((pt.x - bounds.x) / bounds.width) * 0.75));

              this.state.style['size'] = size;
            },
            false,
            true
          ),
        ];

        if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
          handles.push(createArcHandle(state));
        }

        return handles;
      };
    }

    function createDisplayHandleFunction(defaultValue, allowArcHandle, max, redrawEdges, fixedDefaultValue) {
      max = max != null ? max : 0.5;

      return function (state) {
        var handles = [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var fixed =
                fixedDefaultValue != null ? mxUtils.getValue(this.state.style, 'fixedSize', '0') != '0' : null;
              var size = parseFloat(
                mxUtils.getValue(this.state.style, 'size', fixed ? fixedDefaultValue : defaultValue)
              );

              return new mxPoint(
                bounds.x + Math.max(0, Math.min(bounds.width * 0.5, size * (fixed ? 1 : bounds.width))),
                bounds.getCenterY()
              );
            },
            function (bounds, pt, me) {
              var fixed =
                fixedDefaultValue != null ? mxUtils.getValue(this.state.style, 'fixedSize', '0') != '0' : null;
              var size = fixed ? pt.x - bounds.x : Math.max(0, Math.min(max, (pt.x - bounds.x) / bounds.width));

              this.state.style['size'] = size;
            },
            false,
            redrawEdges
          ),
        ];

        if (allowArcHandle && mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
          handles.push(createArcHandle(state));
        }

        return handles;
      };
    }

    function createCubeHandleFunction(factor, defaultValue, allowArcHandle) {
      return function (state) {
        var handles = [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var size =
                Math.max(
                  0,
                  Math.min(
                    bounds.width,
                    Math.min(bounds.height, parseFloat(mxUtils.getValue(this.state.style, 'size', defaultValue)))
                  )
                ) * factor;

              return new mxPoint(bounds.x + size, bounds.y + size);
            },
            function (bounds, pt) {
              this.state.style['size'] = Math.round(
                Math.max(
                  0,
                  Math.min(Math.min(bounds.width, pt.x - bounds.x), Math.min(bounds.height, pt.y - bounds.y))
                ) / factor
              );
            },
            false
          ),
        ];

        if (allowArcHandle && mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
          handles.push(createArcHandle(state));
        }

        return handles;
      };
    }

    function createCylinderHandleFunction(defaultValue) {
      return function (state) {
        return [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var size = Math.max(
                0,
                Math.min(bounds.height * 0.5, parseFloat(mxUtils.getValue(this.state.style, 'size', defaultValue)))
              );

              return new mxPoint(bounds.x, bounds.y + size);
            },
            function (bounds, pt) {
              this.state.style['size'] = Math.max(0, pt.y - bounds.y);
            },
            true
          ),
        ];
      };
    }

    function createArrowHandleFunction(maxSize) {
      return function (state) {
        return [
          createHandle(
            state,
            ['arrowWidth', 'arrowSize'],
            function (bounds) {
              var aw = Math.max(
                0,
                Math.min(1, mxUtils.getValue(this.state.style, 'arrowWidth', SingleArrowShape.prototype.arrowWidth))
              );
              var as = Math.max(
                0,
                Math.min(maxSize, mxUtils.getValue(this.state.style, 'arrowSize', SingleArrowShape.prototype.arrowSize))
              );

              return new mxPoint(bounds.x + (1 - as) * bounds.width, bounds.y + ((1 - aw) * bounds.height) / 2);
            },
            function (bounds, pt) {
              this.state.style['arrowWidth'] = Math.max(
                0,
                Math.min(1, (Math.abs(bounds.y + bounds.height / 2 - pt.y) / bounds.height) * 2)
              );
              this.state.style['arrowSize'] = Math.max(
                0,
                Math.min(maxSize, (bounds.x + bounds.width - pt.x) / bounds.width)
              );
            }
          ),
        ];
      };
    }

    function createEdgeHandle(state, keys, start, getPosition, setPosition) {
      return createHandle(
        state,
        keys,
        function (bounds) {
          var pts = state.absolutePoints;
          var n = pts.length - 1;

          var tr = state.view.translate;
          var s = state.view.scale;

          var p0 = start ? pts[0] : pts[n];
          var p1 = start ? pts[1] : pts[n - 1];
          var dx = start ? p1.x - p0.x : p1.x - p0.x;
          var dy = start ? p1.y - p0.y : p1.y - p0.y;

          var dist = Math.sqrt(dx * dx + dy * dy);

          var pt = getPosition.call(this, dist, dx / dist, dy / dist, p0, p1);

          return new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y);
        },
        function (bounds, pt, me) {
          var pts = state.absolutePoints;
          var n = pts.length - 1;

          var tr = state.view.translate;
          var s = state.view.scale;

          var p0 = start ? pts[0] : pts[n];
          var p1 = start ? pts[1] : pts[n - 1];
          var dx = start ? p1.x - p0.x : p1.x - p0.x;
          var dy = start ? p1.y - p0.y : p1.y - p0.y;

          var dist = Math.sqrt(dx * dx + dy * dy);
          pt.x = (pt.x + tr.x) * s;
          pt.y = (pt.y + tr.y) * s;

          setPosition.call(this, dist, dx / dist, dy / dist, p0, p1, pt, me);
        }
      );
    }

    function createEdgeWidthHandle(state, start, spacing) {
      return createEdgeHandle(
        state,
        ['width'],
        start,
        function (dist, nx, ny, p0, p1) {
          var w = state.shape.getEdgeWidth() * state.view.scale + spacing;

          return new mxPoint(p0.x + (nx * dist) / 4 + (ny * w) / 2, p0.y + (ny * dist) / 4 - (nx * w) / 2);
        },
        function (dist, nx, ny, p0, p1, pt) {
          var w = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, p1.x, p1.y, pt.x, pt.y));
          state.style['width'] = Math.round(w * 2) / state.view.scale - spacing;
        }
      );
    }

    function ptLineDistance(x1, y1, x2, y2, x0, y0) {
      return (
        Math.abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1) /
        Math.sqrt((y2 - y1) * (y2 - y1) + (x2 - x1) * (x2 - x1))
      );
    }

    var handleFactory = {
      link: function (state) {
        var spacing = 10;

        return [createEdgeWidthHandle(state, true, spacing), createEdgeWidthHandle(state, false, spacing)];
      },
      flexArrow: function (state) {
        // Do not use state.shape.startSize/endSize since it is cached
        var tol = state.view.graph.gridSize / state.view.scale;
        var handles = [];

        if (mxUtils.getValue(state.style, mxConstants.STYLE_STARTARROW, mxConstants.NONE) != mxConstants.NONE) {
          handles.push(
            createEdgeHandle(
              state,
              ['width', mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE],
              true,
              function (dist, nx, ny, p0, p1) {
                var w = (state.shape.getEdgeWidth() - state.shape.strokewidth) * state.view.scale;
                var l =
                  mxUtils.getNumber(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) *
                  3 *
                  state.view.scale;

                return new mxPoint(
                  p0.x + nx * (l + state.shape.strokewidth * state.view.scale) + (ny * w) / 2,
                  p0.y + ny * (l + state.shape.strokewidth * state.view.scale) - (nx * w) / 2
                );
              },
              function (dist, nx, ny, p0, p1, pt, me) {
                var w = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, p1.x, p1.y, pt.x, pt.y));
                var l = mxUtils.ptLineDist(p0.x, p0.y, p0.x + ny, p0.y - nx, pt.x, pt.y);

                state.style[mxConstants.STYLE_STARTSIZE] =
                  Math.round(((l - state.shape.strokewidth) * 100) / 3) / 100 / state.view.scale;
                state.style['width'] = Math.round(w * 2) / state.view.scale;

                // Applies to opposite side
                if (mxEvent.isControlDown(me.getEvent())) {
                  state.style[mxConstants.STYLE_ENDSIZE] = state.style[mxConstants.STYLE_STARTSIZE];
                }

                // Snaps to end geometry
                if (!mxEvent.isAltDown(me.getEvent())) {
                  if (
                    Math.abs(
                      parseFloat(state.style[mxConstants.STYLE_STARTSIZE]) -
                        parseFloat(state.style[mxConstants.STYLE_ENDSIZE])
                    ) <
                    tol / 6
                  ) {
                    state.style[mxConstants.STYLE_STARTSIZE] = state.style[mxConstants.STYLE_ENDSIZE];
                  }
                }
              }
            )
          );

          handles.push(
            createEdgeHandle(
              state,
              ['startWidth', 'endWidth', mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE],
              true,
              function (dist, nx, ny, p0, p1) {
                var w = (state.shape.getStartArrowWidth() - state.shape.strokewidth) * state.view.scale;
                var l =
                  mxUtils.getNumber(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.ARROW_SIZE / 5) *
                  3 *
                  state.view.scale;

                return new mxPoint(
                  p0.x + nx * (l + state.shape.strokewidth * state.view.scale) + (ny * w) / 2,
                  p0.y + ny * (l + state.shape.strokewidth * state.view.scale) - (nx * w) / 2
                );
              },
              function (dist, nx, ny, p0, p1, pt, me) {
                var w = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, p1.x, p1.y, pt.x, pt.y));
                var l = mxUtils.ptLineDist(p0.x, p0.y, p0.x + ny, p0.y - nx, pt.x, pt.y);

                state.style[mxConstants.STYLE_STARTSIZE] =
                  Math.round(((l - state.shape.strokewidth) * 100) / 3) / 100 / state.view.scale;
                state.style['startWidth'] =
                  Math.max(0, Math.round(w * 2) - state.shape.getEdgeWidth()) / state.view.scale;

                // Applies to opposite side
                if (mxEvent.isControlDown(me.getEvent())) {
                  state.style[mxConstants.STYLE_ENDSIZE] = state.style[mxConstants.STYLE_STARTSIZE];
                  state.style['endWidth'] = state.style['startWidth'];
                }

                // Snaps to endWidth
                if (!mxEvent.isAltDown(me.getEvent())) {
                  if (
                    Math.abs(
                      parseFloat(state.style[mxConstants.STYLE_STARTSIZE]) -
                        parseFloat(state.style[mxConstants.STYLE_ENDSIZE])
                    ) <
                    tol / 6
                  ) {
                    state.style[mxConstants.STYLE_STARTSIZE] = state.style[mxConstants.STYLE_ENDSIZE];
                  }

                  if (Math.abs(parseFloat(state.style['startWidth']) - parseFloat(state.style['endWidth'])) < tol) {
                    state.style['startWidth'] = state.style['endWidth'];
                  }
                }
              }
            )
          );
        }

        if (mxUtils.getValue(state.style, mxConstants.STYLE_ENDARROW, mxConstants.NONE) != mxConstants.NONE) {
          handles.push(
            createEdgeHandle(
              state,
              ['width', mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE],
              false,
              function (dist, nx, ny, p0, p1) {
                var w = (state.shape.getEdgeWidth() - state.shape.strokewidth) * state.view.scale;
                var l =
                  mxUtils.getNumber(state.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) *
                  3 *
                  state.view.scale;

                return new mxPoint(
                  p0.x + nx * (l + state.shape.strokewidth * state.view.scale) - (ny * w) / 2,
                  p0.y + ny * (l + state.shape.strokewidth * state.view.scale) + (nx * w) / 2
                );
              },
              function (dist, nx, ny, p0, p1, pt, me) {
                var w = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, p1.x, p1.y, pt.x, pt.y));
                var l = mxUtils.ptLineDist(p0.x, p0.y, p0.x + ny, p0.y - nx, pt.x, pt.y);

                state.style[mxConstants.STYLE_ENDSIZE] =
                  Math.round(((l - state.shape.strokewidth) * 100) / 3) / 100 / state.view.scale;
                state.style['width'] = Math.round(w * 2) / state.view.scale;

                // Applies to opposite side
                if (mxEvent.isControlDown(me.getEvent())) {
                  state.style[mxConstants.STYLE_STARTSIZE] = state.style[mxConstants.STYLE_ENDSIZE];
                }

                // Snaps to start geometry
                if (!mxEvent.isAltDown(me.getEvent())) {
                  if (
                    Math.abs(
                      parseFloat(state.style[mxConstants.STYLE_ENDSIZE]) -
                        parseFloat(state.style[mxConstants.STYLE_STARTSIZE])
                    ) <
                    tol / 6
                  ) {
                    state.style[mxConstants.STYLE_ENDSIZE] = state.style[mxConstants.STYLE_STARTSIZE];
                  }
                }
              }
            )
          );

          handles.push(
            createEdgeHandle(
              state,
              ['startWidth', 'endWidth', mxConstants.STYLE_STARTSIZE, mxConstants.STYLE_ENDSIZE],
              false,
              function (dist, nx, ny, p0, p1) {
                var w = (state.shape.getEndArrowWidth() - state.shape.strokewidth) * state.view.scale;
                var l =
                  mxUtils.getNumber(state.style, mxConstants.STYLE_ENDSIZE, mxConstants.ARROW_SIZE / 5) *
                  3 *
                  state.view.scale;

                return new mxPoint(
                  p0.x + nx * (l + state.shape.strokewidth * state.view.scale) - (ny * w) / 2,
                  p0.y + ny * (l + state.shape.strokewidth * state.view.scale) + (nx * w) / 2
                );
              },
              function (dist, nx, ny, p0, p1, pt, me) {
                var w = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, p1.x, p1.y, pt.x, pt.y));
                var l = mxUtils.ptLineDist(p0.x, p0.y, p0.x + ny, p0.y - nx, pt.x, pt.y);

                state.style[mxConstants.STYLE_ENDSIZE] =
                  Math.round(((l - state.shape.strokewidth) * 100) / 3) / 100 / state.view.scale;
                state.style['endWidth'] =
                  Math.max(0, Math.round(w * 2) - state.shape.getEdgeWidth()) / state.view.scale;

                // Applies to opposite side
                if (mxEvent.isControlDown(me.getEvent())) {
                  state.style[mxConstants.STYLE_STARTSIZE] = state.style[mxConstants.STYLE_ENDSIZE];
                  state.style['startWidth'] = state.style['endWidth'];
                }

                // Snaps to start geometry
                if (!mxEvent.isAltDown(me.getEvent())) {
                  if (
                    Math.abs(
                      parseFloat(state.style[mxConstants.STYLE_ENDSIZE]) -
                        parseFloat(state.style[mxConstants.STYLE_STARTSIZE])
                    ) <
                    tol / 6
                  ) {
                    state.style[mxConstants.STYLE_ENDSIZE] = state.style[mxConstants.STYLE_STARTSIZE];
                  }

                  if (Math.abs(parseFloat(state.style['endWidth']) - parseFloat(state.style['startWidth'])) < tol) {
                    state.style['endWidth'] = state.style['startWidth'];
                  }
                }
              }
            )
          );
        }

        return handles;
      },
      swimlane: function (state) {
        var handles = [];

        if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED)) {
          var size = parseFloat(
            mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE)
          );
          handles.push(createArcHandle(state, size / 2));
        }

        // Start size handle must be last item in handles for hover to work in tables (see mouse event handler in Graph)
        handles.push(
          createHandle(
            state,
            [mxConstants.STYLE_STARTSIZE],
            function (bounds) {
              var size = parseFloat(
                mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE)
              );

              if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 1) {
                return new mxPoint(bounds.getCenterX(), bounds.y + Math.max(0, Math.min(bounds.height, size)));
              } else {
                return new mxPoint(bounds.x + Math.max(0, Math.min(bounds.width, size)), bounds.getCenterY());
              }
            },
            function (bounds, pt) {
              state.style[mxConstants.STYLE_STARTSIZE] =
                mxUtils.getValue(this.state.style, mxConstants.STYLE_HORIZONTAL, 1) == 1
                  ? Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)))
                  : Math.round(Math.max(0, Math.min(bounds.width, pt.x - bounds.x)));
            },
            false,
            null,
            function (me) {
              if (mxEvent.isControlDown(me.getEvent())) {
                var graph = state.view.graph;

                if (graph.isTableRow(state.cell) || graph.isTableCell(state.cell)) {
                  var dir = graph.getSwimlaneDirection(state.style);
                  var parent = graph.model.getParent(state.cell);
                  var cells = graph.model.getChildCells(parent, true);
                  var temp = [];

                  for (var i = 0; i < cells.length; i++) {
                    // Finds siblings with the same direction and to set start size
                    if (
                      cells[i] != state.cell &&
                      graph.isSwimlane(cells[i]) &&
                      graph.getSwimlaneDirection(graph.getCurrentCellStyle(cells[i])) == dir
                    ) {
                      temp.push(cells[i]);
                    }
                  }

                  graph.setCellStyles(mxConstants.STYLE_STARTSIZE, state.style[mxConstants.STYLE_STARTSIZE], temp);
                }
              }
            }
          )
        );

        return handles;
      },
      label: createArcHandleFunction(),
      ext: createArcHandleFunction(),
      rectangle: createArcHandleFunction(),
      triangle: createArcHandleFunction(),
      rhombus: createArcHandleFunction(),
      umlLifeline: function (state) {
        return [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var size = Math.max(
                0,
                Math.min(
                  bounds.height,
                  parseFloat(mxUtils.getValue(this.state.style, 'size', UmlLifeline.prototype.size))
                )
              );

              return new mxPoint(bounds.getCenterX(), bounds.y + size);
            },
            function (bounds, pt) {
              this.state.style['size'] = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)));
            },
            false
          ),
        ];
      },
      umlFrame: function (state) {
        var handles = [
          createHandle(
            state,
            ['width', 'height'],
            function (bounds) {
              var w0 = Math.max(
                UmlFrame.prototype.corner,
                Math.min(bounds.width, mxUtils.getValue(this.state.style, 'width', UmlFrame.prototype.width))
              );
              var h0 = Math.max(
                UmlFrame.prototype.corner * 1.5,
                Math.min(bounds.height, mxUtils.getValue(this.state.style, 'height', UmlFrame.prototype.height))
              );

              return new mxPoint(bounds.x + w0, bounds.y + h0);
            },
            function (bounds, pt) {
              this.state.style['width'] = Math.round(
                Math.max(UmlFrame.prototype.corner, Math.min(bounds.width, pt.x - bounds.x))
              );
              this.state.style['height'] = Math.round(
                Math.max(UmlFrame.prototype.corner * 1.5, Math.min(bounds.height, pt.y - bounds.y))
              );
            },
            false
          ),
        ];

        return handles;
      },
      process: function (state) {
        var handles = [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var fixed = mxUtils.getValue(this.state.style, 'fixedSize', '0') != '0';
              var size = parseFloat(mxUtils.getValue(this.state.style, 'size', ProcessShape.prototype.size));

              return fixed
                ? new mxPoint(bounds.x + size, bounds.y + bounds.height / 4)
                : new mxPoint(bounds.x + bounds.width * size, bounds.y + bounds.height / 4);
            },
            function (bounds, pt) {
              var fixed = mxUtils.getValue(this.state.style, 'fixedSize', '0') != '0';
              var size = fixed
                ? Math.max(0, Math.min(bounds.width * 0.5, pt.x - bounds.x))
                : Math.max(0, Math.min(0.5, (pt.x - bounds.x) / bounds.width));
              this.state.style['size'] = size;
            },
            false
          ),
        ];

        if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
          handles.push(createArcHandle(state));
        }

        return handles;
      },
      cross: function (state) {
        return [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var m = Math.min(bounds.width, bounds.height);
              var size =
                (Math.max(0, Math.min(1, mxUtils.getValue(this.state.style, 'size', CrossShape.prototype.size))) * m) /
                2;

              return new mxPoint(bounds.getCenterX() - size, bounds.getCenterY() - size);
            },
            function (bounds, pt) {
              var m = Math.min(bounds.width, bounds.height);
              this.state.style['size'] = Math.max(
                0,
                Math.min(
                  1,
                  Math.min(
                    (Math.max(0, bounds.getCenterY() - pt.y) / m) * 2,
                    (Math.max(0, bounds.getCenterX() - pt.x) / m) * 2
                  )
                )
              );
            }
          ),
        ];
      },
      note: function (state) {
        return [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var size = Math.max(
                0,
                Math.min(
                  bounds.width,
                  Math.min(
                    bounds.height,
                    parseFloat(mxUtils.getValue(this.state.style, 'size', NoteShape.prototype.size))
                  )
                )
              );

              return new mxPoint(bounds.x + bounds.width - size, bounds.y + size);
            },
            function (bounds, pt) {
              this.state.style['size'] = Math.round(
                Math.max(
                  0,
                  Math.min(
                    Math.min(bounds.width, bounds.x + bounds.width - pt.x),
                    Math.min(bounds.height, pt.y - bounds.y)
                  )
                )
              );
            }
          ),
        ];
      },
      note2: function (state) {
        return [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var size = Math.max(
                0,
                Math.min(
                  bounds.width,
                  Math.min(
                    bounds.height,
                    parseFloat(mxUtils.getValue(this.state.style, 'size', NoteShape2.prototype.size))
                  )
                )
              );

              return new mxPoint(bounds.x + bounds.width - size, bounds.y + size);
            },
            function (bounds, pt) {
              this.state.style['size'] = Math.round(
                Math.max(
                  0,
                  Math.min(
                    Math.min(bounds.width, bounds.x + bounds.width - pt.x),
                    Math.min(bounds.height, pt.y - bounds.y)
                  )
                )
              );
            }
          ),
        ];
      },
      manualInput: function (state) {
        var handles = [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var size = Math.max(
                0,
                Math.min(bounds.height, mxUtils.getValue(this.state.style, 'size', ManualInputShape.prototype.size))
              );

              return new mxPoint(bounds.x + bounds.width / 4, bounds.y + (size * 3) / 4);
            },
            function (bounds, pt) {
              this.state.style['size'] = Math.round(Math.max(0, Math.min(bounds.height, ((pt.y - bounds.y) * 4) / 3)));
            },
            false
          ),
        ];

        if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
          handles.push(createArcHandle(state));
        }

        return handles;
      },
      dataStorage: function (state) {
        return [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var fixed = mxUtils.getValue(this.state.style, 'fixedSize', '0') != '0';
              var size = parseFloat(
                mxUtils.getValue(
                  this.state.style,
                  'size',
                  fixed ? DataStorageShape.prototype.fixedSize : DataStorageShape.prototype.size
                )
              );

              return new mxPoint(bounds.x + bounds.width - size * (fixed ? 1 : bounds.width), bounds.getCenterY());
            },
            function (bounds, pt) {
              var fixed = mxUtils.getValue(this.state.style, 'fixedSize', '0') != '0';
              var size = fixed
                ? Math.max(0, Math.min(bounds.width, bounds.x + bounds.width - pt.x))
                : Math.max(0, Math.min(1, (bounds.x + bounds.width - pt.x) / bounds.width));

              this.state.style['size'] = size;
            },
            false
          ),
        ];
      },
      callout: function (state) {
        var handles = [
          createHandle(
            state,
            ['size', 'position'],
            function (bounds) {
              var size = Math.max(
                0,
                Math.min(bounds.height, mxUtils.getValue(this.state.style, 'size', CalloutShape.prototype.size))
              );
              var position = Math.max(
                0,
                Math.min(1, mxUtils.getValue(this.state.style, 'position', CalloutShape.prototype.position))
              );
              var base = Math.max(
                0,
                Math.min(bounds.width, mxUtils.getValue(this.state.style, 'base', CalloutShape.prototype.base))
              );

              return new mxPoint(bounds.x + position * bounds.width, bounds.y + bounds.height - size);
            },
            function (bounds, pt) {
              var base = Math.max(
                0,
                Math.min(bounds.width, mxUtils.getValue(this.state.style, 'base', CalloutShape.prototype.base))
              );
              this.state.style['size'] = Math.round(
                Math.max(0, Math.min(bounds.height, bounds.y + bounds.height - pt.y))
              );
              this.state.style['position'] =
                Math.round(Math.max(0, Math.min(1, (pt.x - bounds.x) / bounds.width)) * 100) / 100;
            },
            false
          ),
          createHandle(
            state,
            ['position2'],
            function (bounds) {
              var position2 = Math.max(
                0,
                Math.min(1, mxUtils.getValue(this.state.style, 'position2', CalloutShape.prototype.position2))
              );

              return new mxPoint(bounds.x + position2 * bounds.width, bounds.y + bounds.height);
            },
            function (bounds, pt) {
              this.state.style['position2'] =
                Math.round(Math.max(0, Math.min(1, (pt.x - bounds.x) / bounds.width)) * 100) / 100;
            },
            false
          ),
          createHandle(
            state,
            ['base'],
            function (bounds) {
              var size = Math.max(
                0,
                Math.min(bounds.height, mxUtils.getValue(this.state.style, 'size', CalloutShape.prototype.size))
              );
              var position = Math.max(
                0,
                Math.min(1, mxUtils.getValue(this.state.style, 'position', CalloutShape.prototype.position))
              );
              var base = Math.max(
                0,
                Math.min(bounds.width, mxUtils.getValue(this.state.style, 'base', CalloutShape.prototype.base))
              );

              return new mxPoint(
                bounds.x + Math.min(bounds.width, position * bounds.width + base),
                bounds.y + bounds.height - size
              );
            },
            function (bounds, pt) {
              var position = Math.max(
                0,
                Math.min(1, mxUtils.getValue(this.state.style, 'position', CalloutShape.prototype.position))
              );

              this.state.style['base'] = Math.round(
                Math.max(0, Math.min(bounds.width, pt.x - bounds.x - position * bounds.width))
              );
            },
            false
          ),
        ];

        if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
          handles.push(createArcHandle(state));
        }

        return handles;
      },
      internalStorage: function (state) {
        var handles = [
          createHandle(
            state,
            ['dx', 'dy'],
            function (bounds) {
              var dx = Math.max(
                0,
                Math.min(bounds.width, mxUtils.getValue(this.state.style, 'dx', InternalStorageShape.prototype.dx))
              );
              var dy = Math.max(
                0,
                Math.min(bounds.height, mxUtils.getValue(this.state.style, 'dy', InternalStorageShape.prototype.dy))
              );

              return new mxPoint(bounds.x + dx, bounds.y + dy);
            },
            function (bounds, pt) {
              this.state.style['dx'] = Math.round(Math.max(0, Math.min(bounds.width, pt.x - bounds.x)));
              this.state.style['dy'] = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)));
            },
            false
          ),
        ];

        if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false)) {
          handles.push(createArcHandle(state));
        }

        return handles;
      },
      module: function (state) {
        var handles = [
          createHandle(
            state,
            ['jettyWidth', 'jettyHeight'],
            function (bounds) {
              var dx = Math.max(
                0,
                Math.min(
                  bounds.width,
                  mxUtils.getValue(this.state.style, 'jettyWidth', ModuleShape.prototype.jettyWidth)
                )
              );
              var dy = Math.max(
                0,
                Math.min(
                  bounds.height,
                  mxUtils.getValue(this.state.style, 'jettyHeight', ModuleShape.prototype.jettyHeight)
                )
              );

              return new mxPoint(bounds.x + dx / 2, bounds.y + dy * 2);
            },
            function (bounds, pt) {
              this.state.style['jettyWidth'] = Math.round(Math.max(0, Math.min(bounds.width, pt.x - bounds.x)) * 2);
              this.state.style['jettyHeight'] = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)) / 2);
            }
          ),
        ];

        return handles;
      },
      corner: function (state) {
        return [
          createHandle(
            state,
            ['dx', 'dy'],
            function (bounds) {
              var dx = Math.max(
                0,
                Math.min(bounds.width, mxUtils.getValue(this.state.style, 'dx', CornerShape.prototype.dx))
              );
              var dy = Math.max(
                0,
                Math.min(bounds.height, mxUtils.getValue(this.state.style, 'dy', CornerShape.prototype.dy))
              );

              return new mxPoint(bounds.x + dx, bounds.y + dy);
            },
            function (bounds, pt) {
              this.state.style['dx'] = Math.round(Math.max(0, Math.min(bounds.width, pt.x - bounds.x)));
              this.state.style['dy'] = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)));
            },
            false
          ),
        ];
      },
      tee: function (state) {
        return [
          createHandle(
            state,
            ['dx', 'dy'],
            function (bounds) {
              var dx = Math.max(
                0,
                Math.min(bounds.width, mxUtils.getValue(this.state.style, 'dx', TeeShape.prototype.dx))
              );
              var dy = Math.max(
                0,
                Math.min(bounds.height, mxUtils.getValue(this.state.style, 'dy', TeeShape.prototype.dy))
              );

              return new mxPoint(bounds.x + (bounds.width + dx) / 2, bounds.y + dy);
            },
            function (bounds, pt) {
              this.state.style['dx'] = Math.round(
                Math.max(0, Math.min(bounds.width / 2, pt.x - bounds.x - bounds.width / 2) * 2)
              );
              this.state.style['dy'] = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)));
            },
            false
          ),
        ];
      },
      singleArrow: createArrowHandleFunction(1),
      doubleArrow: createArrowHandleFunction(0.5),
      folder: function (state) {
        return [
          createHandle(
            state,
            ['tabWidth', 'tabHeight'],
            function (bounds) {
              var tw = Math.max(
                0,
                Math.min(bounds.width, mxUtils.getValue(this.state.style, 'tabWidth', FolderShape.prototype.tabWidth))
              );
              var th = Math.max(
                0,
                Math.min(
                  bounds.height,
                  mxUtils.getValue(this.state.style, 'tabHeight', FolderShape.prototype.tabHeight)
                )
              );

              if (
                mxUtils.getValue(this.state.style, 'tabPosition', FolderShape.prototype.tabPosition) ==
                mxConstants.ALIGN_RIGHT
              ) {
                tw = bounds.width - tw;
              }

              return new mxPoint(bounds.x + tw, bounds.y + th);
            },
            function (bounds, pt) {
              var tw = Math.max(0, Math.min(bounds.width, pt.x - bounds.x));

              if (
                mxUtils.getValue(this.state.style, 'tabPosition', FolderShape.prototype.tabPosition) ==
                mxConstants.ALIGN_RIGHT
              ) {
                tw = bounds.width - tw;
              }

              this.state.style['tabWidth'] = Math.round(tw);
              this.state.style['tabHeight'] = Math.round(Math.max(0, Math.min(bounds.height, pt.y - bounds.y)));
            },
            false
          ),
        ];
      },
      document: function (state) {
        return [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var size = Math.max(
                0,
                Math.min(1, parseFloat(mxUtils.getValue(this.state.style, 'size', DocumentShape.prototype.size)))
              );

              return new mxPoint(bounds.x + (3 * bounds.width) / 4, bounds.y + (1 - size) * bounds.height);
            },
            function (bounds, pt) {
              this.state.style['size'] = Math.max(0, Math.min(1, (bounds.y + bounds.height - pt.y) / bounds.height));
            },
            false
          ),
        ];
      },
      tape: function (state) {
        return [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var size = Math.max(
                0,
                Math.min(1, parseFloat(mxUtils.getValue(this.state.style, 'size', TapeShape.prototype.size)))
              );

              return new mxPoint(bounds.getCenterX(), bounds.y + (size * bounds.height) / 2);
            },
            function (bounds, pt) {
              this.state.style['size'] = Math.max(0, Math.min(1, ((pt.y - bounds.y) / bounds.height) * 2));
            },
            false
          ),
        ];
      },
      isoCube2: function (state) {
        return [
          createHandle(
            state,
            ['isoAngle'],
            function (bounds) {
              var isoAngle =
                (Math.max(
                  0.01,
                  Math.min(94, parseFloat(mxUtils.getValue(this.state.style, 'isoAngle', IsoCubeShape2.isoAngle)))
                ) *
                  Math.PI) /
                200;
              var isoH = Math.min(bounds.width * Math.tan(isoAngle), bounds.height * 0.5);

              return new mxPoint(bounds.x, bounds.y + isoH);
            },
            function (bounds, pt) {
              this.state.style['isoAngle'] = Math.max(0, ((pt.y - bounds.y) * 50) / bounds.height);
            },
            true
          ),
        ];
      },
      cylinder2: createCylinderHandleFunction(CylinderShape.prototype.size),
      cylinder3: createCylinderHandleFunction(CylinderShape3.prototype.size),
      offPageConnector: function (state) {
        return [
          createHandle(
            state,
            ['size'],
            function (bounds) {
              var size = Math.max(
                0,
                Math.min(
                  1,
                  parseFloat(mxUtils.getValue(this.state.style, 'size', OffPageConnectorShape.prototype.size))
                )
              );

              return new mxPoint(bounds.getCenterX(), bounds.y + (1 - size) * bounds.height);
            },
            function (bounds, pt) {
              this.state.style['size'] = Math.max(0, Math.min(1, (bounds.y + bounds.height - pt.y) / bounds.height));
            },
            false
          ),
        ];
      },
      step: createDisplayHandleFunction(StepShape.prototype.size, true, null, true, StepShape.prototype.fixedSize),
      hexagon: createDisplayHandleFunction(
        HexagonShape.prototype.size,
        true,
        0.5,
        true,
        HexagonShape.prototype.fixedSize
      ),
      curlyBracket: createDisplayHandleFunction(CurlyBracketShape.prototype.size, false),
      display: createDisplayHandleFunction(DisplayShape.prototype.size, false),
      cube: createCubeHandleFunction(1, CubeShape.prototype.size, false),
      card: createCubeHandleFunction(0.5, CardShape.prototype.size, true),
      loopLimit: createCubeHandleFunction(0.5, LoopLimitShape.prototype.size, true),
      trapezoid: createTrapezoidHandleFunction(0.5, TrapezoidShape.prototype.size, TrapezoidShape.prototype.fixedSize),
      parallelogram: createTrapezoidHandleFunction(
        1,
        ParallelogramShape.prototype.size,
        ParallelogramShape.prototype.fixedSize
      ),
    };

    // Exposes custom handles
    Graph.createHandle = createHandle;
    Graph.handleFactory = handleFactory;

    var vertexHandlerCreateCustomHandles = mxVertexHandler.prototype.createCustomHandles;

    mxVertexHandler.prototype.createCustomHandles = function () {
      var handles = vertexHandlerCreateCustomHandles.apply(this, arguments);

      if (this.graph.isCellRotatable(this.state.cell)) {
        // LATER: Make locked state independent of rotatable flag, fix toggle if default is false
        //if (this.graph.isCellResizable(this.state.cell) || this.graph.isCellMovable(this.state.cell))
        var name = this.state.style['shape'];

        if (mxCellRenderer.defaultShapes[name] == null && mxStencilRegistry.getStencil(name) == null) {
          name = mxConstants.SHAPE_RECTANGLE;
        } else if (this.state.view.graph.isSwimlane(this.state.cell)) {
          name = mxConstants.SHAPE_SWIMLANE;
        }

        var fn = handleFactory[name];

        if (fn == null && this.state.shape != null && this.state.shape.isRoundable()) {
          fn = handleFactory[mxConstants.SHAPE_RECTANGLE];
        }

        if (fn != null) {
          var temp = fn(this.state);

          if (temp != null) {
            if (handles == null) {
              handles = temp;
            } else {
              handles = handles.concat(temp);
            }
          }
        }
      }

      return handles;
    };

    mxEdgeHandler.prototype.createCustomHandles = function () {
      var name = this.state.style['shape'];

      if (mxCellRenderer.defaultShapes[name] == null && mxStencilRegistry.getStencil(name) == null) {
        name = mxConstants.SHAPE_CONNECTOR;
      }

      var fn = handleFactory[name];

      if (fn != null) {
        return fn(this.state);
      }

      return null;
    };
  } else {
    // Dummy entries to avoid NPE in embed mode
    Graph.createHandle = function () {};
    Graph.handleFactory = {};
  }

  var isoHVector = new mxPoint(1, 0);
  var isoVVector = new mxPoint(1, 0);

  var alpha1 = mxUtils.toRadians(-30);

  var cos1 = Math.cos(alpha1);
  var sin1 = Math.sin(alpha1);

  isoHVector = mxUtils.getRotatedPoint(isoHVector, cos1, sin1);

  var alpha2 = mxUtils.toRadians(-150);

  var cos2 = Math.cos(alpha2);
  var sin2 = Math.sin(alpha2);

  isoVVector = mxUtils.getRotatedPoint(isoVVector, cos2, sin2);

  mxEdgeStyle.IsometricConnector = function (state, source, target, points, result) {
    var view = state.view;
    var pt = points != null && points.length > 0 ? points[0] : null;
    var pts = state.absolutePoints;
    var p0 = pts[0];
    var pe = pts[pts.length - 1];

    if (pt != null) {
      pt = view.transformControlPoint(state, pt);
    }

    if (p0 == null) {
      if (source != null) {
        p0 = new mxPoint(source.getCenterX(), source.getCenterY());
      }
    }

    if (pe == null) {
      if (target != null) {
        pe = new mxPoint(target.getCenterX(), target.getCenterY());
      }
    }

    var a1 = isoHVector.x;
    var a2 = isoHVector.y;

    var b1 = isoVVector.x;
    var b2 = isoVVector.y;

    var elbow = mxUtils.getValue(state.style, 'elbow', 'horizontal') == 'horizontal';

    if (pe != null && p0 != null) {
      var last = p0;

      function isoLineTo(x, y, ignoreFirst) {
        var c1 = x - last.x;
        var c2 = y - last.y;

        // Solves for isometric base vectors
        var h = (b2 * c1 - b1 * c2) / (a1 * b2 - a2 * b1);
        var v = (a2 * c1 - a1 * c2) / (a2 * b1 - a1 * b2);

        if (elbow) {
          if (ignoreFirst) {
            last = new mxPoint(last.x + a1 * h, last.y + a2 * h);
            result.push(last);
          }

          last = new mxPoint(last.x + b1 * v, last.y + b2 * v);
          result.push(last);
        } else {
          if (ignoreFirst) {
            last = new mxPoint(last.x + b1 * v, last.y + b2 * v);
            result.push(last);
          }

          last = new mxPoint(last.x + a1 * h, last.y + a2 * h);
          result.push(last);
        }
      }

      if (pt == null) {
        pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
      }

      isoLineTo(pt.x, pt.y, true);
      isoLineTo(pe.x, pe.y, false);
    }
  };

  mxStyleRegistry.putValue('isometricEdgeStyle', mxEdgeStyle.IsometricConnector);

  var graphCreateEdgeHandler = Graph.prototype.createEdgeHandler;
  Graph.prototype.createEdgeHandler = function (state, edgeStyle) {
    if (edgeStyle == mxEdgeStyle.IsometricConnector) {
      var handler = new mxElbowEdgeHandler(state);
      handler.snapToTerminals = false;

      return handler;
    }

    return graphCreateEdgeHandler.apply(this, arguments);
  };

  // Defines connection points for all shapes
  IsoRectangleShape.prototype.constraints = [];

  IsoCubeShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var tan30 = Math.tan(mxUtils.toRadians(30));
    var tan30Dx = (0.5 - tan30) / 2;
    var m = Math.min(w, h / (0.5 + tan30));
    var dx = (w - m) / 2;
    var dy = (h - m) / 2;

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx, dy + 0.25 * m));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx + 0.5 * m, dy + m * tan30Dx));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx + m, dy + 0.25 * m));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx + m, dy + 0.75 * m));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx + 0.5 * m, dy + (1 - tan30Dx) * m));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx, dy + 0.75 * m));

    return constr;
  };

  IsoCubeShape2.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var isoAngle =
      (Math.max(0.01, Math.min(94, parseFloat(mxUtils.getValue(this.style, 'isoAngle', this.isoAngle)))) * Math.PI) /
      200;
    var isoH = Math.min(w * Math.tan(isoAngle), h * 0.5);

    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, isoH));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0.5), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, h - isoH));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, h - isoH));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0.5), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, isoH));

    return constr;
  };

  CalloutShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var arcSize = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2;
    var s = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var dx = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'position', this.position))));
    var dx2 = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'position2', this.position2))));
    var base = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'base', this.base))));

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0.25, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0.75, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, (h - s) * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, h - s));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx2, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, h - s));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, (h - s) * 0.5));

    if (w >= s * 2) {
      constr.push(new mxConnectionConstraint(new mxPoint(0.5, 0), false));
    }

    return constr;
  };

  mxRectangleShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0, 0), true),
    new mxConnectionConstraint(new mxPoint(0.25, 0), true),
    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
    new mxConnectionConstraint(new mxPoint(0.75, 0), true),
    new mxConnectionConstraint(new mxPoint(1, 0), true),
    new mxConnectionConstraint(new mxPoint(0, 0.25), true),
    new mxConnectionConstraint(new mxPoint(0, 0.5), true),
    new mxConnectionConstraint(new mxPoint(0, 0.75), true),
    new mxConnectionConstraint(new mxPoint(1, 0.25), true),
    new mxConnectionConstraint(new mxPoint(1, 0.5), true),
    new mxConnectionConstraint(new mxPoint(1, 0.75), true),
    new mxConnectionConstraint(new mxPoint(0, 1), true),
    new mxConnectionConstraint(new mxPoint(0.25, 1), true),
    new mxConnectionConstraint(new mxPoint(0.5, 1), true),
    new mxConnectionConstraint(new mxPoint(0.75, 1), true),
    new mxConnectionConstraint(new mxPoint(1, 1), true),
  ];
  mxEllipse.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0, 0), true),
    new mxConnectionConstraint(new mxPoint(1, 0), true),
    new mxConnectionConstraint(new mxPoint(0, 1), true),
    new mxConnectionConstraint(new mxPoint(1, 1), true),
    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
    new mxConnectionConstraint(new mxPoint(0.5, 1), true),
    new mxConnectionConstraint(new mxPoint(0, 0.5), true),
    new mxConnectionConstraint(new mxPoint(1, 0.5)),
  ];
  PartialRectangleShape.prototype.constraints = mxRectangleShape.prototype.constraints;
  mxImageShape.prototype.constraints = mxRectangleShape.prototype.constraints;
  mxSwimlane.prototype.constraints = mxRectangleShape.prototype.constraints;
  PlusShape.prototype.constraints = mxRectangleShape.prototype.constraints;
  mxLabel.prototype.constraints = mxRectangleShape.prototype.constraints;

  NoteShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var s = Math.max(0, Math.min(w, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)))));

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w - s) * 0.5, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - s, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - s * 0.5, s * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, s));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, (h + s) * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0.5), false));

    if (w >= s * 2) {
      constr.push(new mxConnectionConstraint(new mxPoint(0.5, 0), false));
    }

    return constr;
  };

  CardShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var s = Math.max(0, Math.min(w, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)))));

    constr.push(new mxConnectionConstraint(new mxPoint(1, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w + s) * 0.5, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, s, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, s * 0.5, s * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, s));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, (h + s) * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0.5), false));

    if (w >= s * 2) {
      constr.push(new mxConnectionConstraint(new mxPoint(0.5, 0), false));
    }

    return constr;
  };

  CubeShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var s = Math.max(0, Math.min(w, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size)))));

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w - s) * 0.5, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - s, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - s * 0.5, s * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, s));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, (h + s) * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w + s) * 0.5, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, s, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, s * 0.5, h - s * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, h - s));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, (h - s) * 0.5));

    return constr;
  };

  CylinderShape3.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var s = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'size', this.size))));

    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0.5), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0.5), false));

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, s));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0), false, null, 0, s));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 1), false, null, 0, -s));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 1), false, null, 0, -s));

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, s + (h * 0.5 - s) * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0), false, null, 0, s + (h * 0.5 - s) * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0), false, null, 0, h - s - (h * 0.5 - s) * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, h - s - (h * 0.5 - s) * 0.5));

    constr.push(new mxConnectionConstraint(new mxPoint(0.145, 0), false, null, 0, s * 0.29));
    constr.push(new mxConnectionConstraint(new mxPoint(0.855, 0), false, null, 0, s * 0.29));
    constr.push(new mxConnectionConstraint(new mxPoint(0.855, 1), false, null, 0, -s * 0.29));
    constr.push(new mxConnectionConstraint(new mxPoint(0.145, 1), false, null, 0, -s * 0.29));

    return constr;
  };

  FolderShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var dx = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'tabWidth', this.tabWidth))));
    var dy = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'tabHeight', this.tabHeight))));
    var tp = mxUtils.getValue(this.style, 'tabPosition', this.tabPosition);

    if (tp == 'left') {
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false));
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx * 0.5, 0));
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx, 0));
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx, dy));
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w + dx) * 0.5, dy));
    } else {
      constr.push(new mxConnectionConstraint(new mxPoint(1, 0), false));
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - dx * 0.5, 0));
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - dx, 0));
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - dx, dy));
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w - dx) * 0.5, dy));
    }

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, (h - dy) * 0.25 + dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, (h - dy) * 0.5 + dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, (h - dy) * 0.75 + dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, (h - dy) * 0.25 + dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, (h - dy) * 0.5 + dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, (h - dy) * 0.75 + dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0.25, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0.75, 1), false));

    return constr;
  };

  InternalStorageShape.prototype.constraints = mxRectangleShape.prototype.constraints;
  DataStorageShape.prototype.constraints = mxRectangleShape.prototype.constraints;
  TapeDataShape.prototype.constraints = mxEllipse.prototype.constraints;
  OrEllipseShape.prototype.constraints = mxEllipse.prototype.constraints;
  SumEllipseShape.prototype.constraints = mxEllipse.prototype.constraints;
  LineEllipseShape.prototype.constraints = mxEllipse.prototype.constraints;
  ManualInputShape.prototype.constraints = mxRectangleShape.prototype.constraints;
  DelayShape.prototype.constraints = mxRectangleShape.prototype.constraints;

  DisplayShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var dx = Math.min(w, h / 2);
    var s = Math.min(w - dx, Math.max(0, parseFloat(mxUtils.getValue(this.style, 'size', this.size))) * w);

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0.5), false, null));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, s, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (s + w - dx) * 0.5, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - dx, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0.5), false, null));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - dx, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (s + w - dx) * 0.5, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, s, h));

    return constr;
  };

  ModuleShape.prototype.getConstraints = function (style, w, h) {
    var x0 = parseFloat(mxUtils.getValue(style, 'jettyWidth', ModuleShape.prototype.jettyWidth)) / 2;
    var dy = parseFloat(mxUtils.getValue(style, 'jettyHeight', ModuleShape.prototype.jettyHeight));
    var constr = [
      new mxConnectionConstraint(new mxPoint(0, 0), false, null, x0),
      new mxConnectionConstraint(new mxPoint(0.25, 0), true),
      new mxConnectionConstraint(new mxPoint(0.5, 0), true),
      new mxConnectionConstraint(new mxPoint(0.75, 0), true),
      new mxConnectionConstraint(new mxPoint(1, 0), true),
      new mxConnectionConstraint(new mxPoint(1, 0.25), true),
      new mxConnectionConstraint(new mxPoint(1, 0.5), true),
      new mxConnectionConstraint(new mxPoint(1, 0.75), true),
      new mxConnectionConstraint(new mxPoint(0, 1), false, null, x0),
      new mxConnectionConstraint(new mxPoint(0.25, 1), true),
      new mxConnectionConstraint(new mxPoint(0.5, 1), true),
      new mxConnectionConstraint(new mxPoint(0.75, 1), true),
      new mxConnectionConstraint(new mxPoint(1, 1), true),
      new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, Math.min(h - 0.5 * dy, 1.5 * dy)),
      new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, Math.min(h - 0.5 * dy, 3.5 * dy)),
    ];

    if (h > 5 * dy) {
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0.75), false, null, x0));
    }

    if (h > 8 * dy) {
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0.5), false, null, x0));
    }

    if (h > 15 * dy) {
      constr.push(new mxConnectionConstraint(new mxPoint(0, 0.25), false, null, x0));
    }

    return constr;
  };

  LoopLimitShape.prototype.constraints = mxRectangleShape.prototype.constraints;
  OffPageConnectorShape.prototype.constraints = mxRectangleShape.prototype.constraints;
  mxCylinder.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0.15, 0.05), false),
    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
    new mxConnectionConstraint(new mxPoint(0.85, 0.05), false),
    new mxConnectionConstraint(new mxPoint(0, 0.3), true),
    new mxConnectionConstraint(new mxPoint(0, 0.5), true),
    new mxConnectionConstraint(new mxPoint(0, 0.7), true),
    new mxConnectionConstraint(new mxPoint(1, 0.3), true),
    new mxConnectionConstraint(new mxPoint(1, 0.5), true),
    new mxConnectionConstraint(new mxPoint(1, 0.7), true),
    new mxConnectionConstraint(new mxPoint(0.15, 0.95), false),
    new mxConnectionConstraint(new mxPoint(0.5, 1), true),
    new mxConnectionConstraint(new mxPoint(0.85, 0.95), false),
  ];
  UmlActorShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0.25, 0.1), false),
    new mxConnectionConstraint(new mxPoint(0.5, 0), false),
    new mxConnectionConstraint(new mxPoint(0.75, 0.1), false),
    new mxConnectionConstraint(new mxPoint(0, 1 / 3), false),
    new mxConnectionConstraint(new mxPoint(0, 1), false),
    new mxConnectionConstraint(new mxPoint(1, 1 / 3), false),
    new mxConnectionConstraint(new mxPoint(1, 1), false),
    new mxConnectionConstraint(new mxPoint(0.5, 0.5), false),
  ];
  ComponentShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0.25, 0), true),
    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
    new mxConnectionConstraint(new mxPoint(0.75, 0), true),
    new mxConnectionConstraint(new mxPoint(0, 0.3), true),
    new mxConnectionConstraint(new mxPoint(0, 0.7), true),
    new mxConnectionConstraint(new mxPoint(1, 0.25), true),
    new mxConnectionConstraint(new mxPoint(1, 0.5), true),
    new mxConnectionConstraint(new mxPoint(1, 0.75), true),
    new mxConnectionConstraint(new mxPoint(0.25, 1), true),
    new mxConnectionConstraint(new mxPoint(0.5, 1), true),
    new mxConnectionConstraint(new mxPoint(0.75, 1), true),
  ];
  mxActor.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
    new mxConnectionConstraint(new mxPoint(0.25, 0.2), false),
    new mxConnectionConstraint(new mxPoint(0.1, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0, 0.75), true),
    new mxConnectionConstraint(new mxPoint(0.75, 0.25), false),
    new mxConnectionConstraint(new mxPoint(0.9, 0.5), false),
    new mxConnectionConstraint(new mxPoint(1, 0.75), true),
    new mxConnectionConstraint(new mxPoint(0.25, 1), true),
    new mxConnectionConstraint(new mxPoint(0.5, 1), true),
    new mxConnectionConstraint(new mxPoint(0.75, 1), true),
  ];
  SwitchShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0, 0), false),
    new mxConnectionConstraint(new mxPoint(0.5, 0.25), false),
    new mxConnectionConstraint(new mxPoint(1, 0), false),
    new mxConnectionConstraint(new mxPoint(0.25, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0.75, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0, 1), false),
    new mxConnectionConstraint(new mxPoint(0.5, 0.75), false),
    new mxConnectionConstraint(new mxPoint(1, 1), false),
  ];
  TapeShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0, 0.35), false),
    new mxConnectionConstraint(new mxPoint(0, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0, 0.65), false),
    new mxConnectionConstraint(new mxPoint(1, 0.35), false),
    new mxConnectionConstraint(new mxPoint(1, 0.5), false),
    new mxConnectionConstraint(new mxPoint(1, 0.65), false),
    new mxConnectionConstraint(new mxPoint(0.25, 1), false),
    new mxConnectionConstraint(new mxPoint(0.75, 0), false),
  ];
  StepShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0.25, 0), true),
    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
    new mxConnectionConstraint(new mxPoint(0.75, 0), true),
    new mxConnectionConstraint(new mxPoint(0.25, 1), true),
    new mxConnectionConstraint(new mxPoint(0.5, 1), true),
    new mxConnectionConstraint(new mxPoint(0.75, 1), true),
    new mxConnectionConstraint(new mxPoint(0, 0.25), true),
    new mxConnectionConstraint(new mxPoint(0, 0.5), true),
    new mxConnectionConstraint(new mxPoint(0, 0.75), true),
    new mxConnectionConstraint(new mxPoint(1, 0.25), true),
    new mxConnectionConstraint(new mxPoint(1, 0.5), true),
    new mxConnectionConstraint(new mxPoint(1, 0.75), true),
  ];
  mxLine.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0.25, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0.75, 0.5), false),
    new mxConnectionConstraint(new mxPoint(1, 0.5), false),
  ];
  LollipopShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0.5, 0), false),
    new mxConnectionConstraint(new mxPoint(0.5, 1), false),
  ];
  mxDoubleEllipse.prototype.constraints = mxEllipse.prototype.constraints;
  mxRhombus.prototype.constraints = mxEllipse.prototype.constraints;
  mxTriangle.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0, 0.25), true),
    new mxConnectionConstraint(new mxPoint(0, 0.5), true),
    new mxConnectionConstraint(new mxPoint(0, 0.75), true),
    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
    new mxConnectionConstraint(new mxPoint(0.5, 1), true),
    new mxConnectionConstraint(new mxPoint(1, 0.5), true),
  ];
  mxHexagon.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0.375, 0), true),
    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
    new mxConnectionConstraint(new mxPoint(0.625, 0), true),
    new mxConnectionConstraint(new mxPoint(0, 0.25), true),
    new mxConnectionConstraint(new mxPoint(0, 0.5), true),
    new mxConnectionConstraint(new mxPoint(0, 0.75), true),
    new mxConnectionConstraint(new mxPoint(1, 0.25), true),
    new mxConnectionConstraint(new mxPoint(1, 0.5), true),
    new mxConnectionConstraint(new mxPoint(1, 0.75), true),
    new mxConnectionConstraint(new mxPoint(0.375, 1), true),
    new mxConnectionConstraint(new mxPoint(0.5, 1), true),
    new mxConnectionConstraint(new mxPoint(0.625, 1), true),
  ];
  mxCloud.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0.25, 0.25), false),
    new mxConnectionConstraint(new mxPoint(0.4, 0.1), false),
    new mxConnectionConstraint(new mxPoint(0.16, 0.55), false),
    new mxConnectionConstraint(new mxPoint(0.07, 0.4), false),
    new mxConnectionConstraint(new mxPoint(0.31, 0.8), false),
    new mxConnectionConstraint(new mxPoint(0.13, 0.77), false),
    new mxConnectionConstraint(new mxPoint(0.8, 0.8), false),
    new mxConnectionConstraint(new mxPoint(0.55, 0.95), false),
    new mxConnectionConstraint(new mxPoint(0.875, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0.96, 0.7), false),
    new mxConnectionConstraint(new mxPoint(0.625, 0.2), false),
    new mxConnectionConstraint(new mxPoint(0.88, 0.25), false),
  ];
  ParallelogramShape.prototype.constraints = mxRectangleShape.prototype.constraints;
  TrapezoidShape.prototype.constraints = mxRectangleShape.prototype.constraints;
  DocumentShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0.25, 0), true),
    new mxConnectionConstraint(new mxPoint(0.5, 0), true),
    new mxConnectionConstraint(new mxPoint(0.75, 0), true),
    new mxConnectionConstraint(new mxPoint(0, 0.25), true),
    new mxConnectionConstraint(new mxPoint(0, 0.5), true),
    new mxConnectionConstraint(new mxPoint(0, 0.75), true),
    new mxConnectionConstraint(new mxPoint(1, 0.25), true),
    new mxConnectionConstraint(new mxPoint(1, 0.5), true),
    new mxConnectionConstraint(new mxPoint(1, 0.75), true),
  ];
  mxArrow.prototype.constraints = null;

  TeeShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var dx = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'dx', this.dx))));
    var dy = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'dy', this.dy))));
    var w2 = Math.abs(w - dx) / 2;

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, dy * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w * 0.75 + dx * 0.25, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w + dx) * 0.5, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w + dx) * 0.5, (h + dy) * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w + dx) * 0.5, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w - dx) * 0.5, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w - dx) * 0.5, (h + dy) * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w - dx) * 0.5, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w * 0.25 - dx * 0.25, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, dy * 0.5));

    return constr;
  };

  CornerShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var dx = Math.max(0, Math.min(w, parseFloat(mxUtils.getValue(this.style, 'dx', this.dx))));
    var dy = Math.max(0, Math.min(h, parseFloat(mxUtils.getValue(this.style, 'dy', this.dy))));

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, dy * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w + dx) * 0.5, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx, dy));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx, (h + dy) * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, dx * 0.5, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0.5), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 1), false));

    return constr;
  };

  CrossbarShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0, 0), false),
    new mxConnectionConstraint(new mxPoint(0, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0, 1), false),
    new mxConnectionConstraint(new mxPoint(0.25, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0.5, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0.75, 0.5), false),
    new mxConnectionConstraint(new mxPoint(1, 0), false),
    new mxConnectionConstraint(new mxPoint(1, 0.5), false),
    new mxConnectionConstraint(new mxPoint(1, 1), false),
  ];

  SingleArrowShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var aw = h * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowWidth', this.arrowWidth))));
    var as = w * Math.max(0, Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowSize', this.arrowSize))));
    var at = (h - aw) / 2;
    var ab = at + aw;

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0.5), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, at));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w - as) * 0.5, at));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - as, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0.5), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - as, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w - as) * 0.5, h - at));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, h - at));

    return constr;
  };

  DoubleArrowShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var aw =
      h *
      Math.max(
        0,
        Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowWidth', SingleArrowShape.prototype.arrowWidth)))
      );
    var as =
      w *
      Math.max(
        0,
        Math.min(1, parseFloat(mxUtils.getValue(this.style, 'arrowSize', SingleArrowShape.prototype.arrowSize)))
      );
    var at = (h - aw) / 2;
    var ab = at + aw;

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0.5), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, as, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w * 0.5, at));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - as, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0.5), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w - as, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w * 0.5, h - at));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, as, h));

    return constr;
  };

  CrossShape.prototype.getConstraints = function (style, w, h) {
    var constr = [];
    var m = Math.min(h, w);
    var size = Math.max(0, Math.min(m, m * parseFloat(mxUtils.getValue(this.style, 'size', this.size))));
    var t = (h - size) / 2;
    var b = t + size;
    var l = (w - size) / 2;
    var r = l + size;

    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, l, t * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, l, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 0), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, r, 0));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, r, t * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, r, t));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, l, h - t * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, l, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0.5, 1), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, r, h));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, r, h - t * 0.5));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, r, b));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w + r) * 0.5, t));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, t));
    constr.push(new mxConnectionConstraint(new mxPoint(1, 0.5), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, w, b));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, (w + r) * 0.5, b));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, l, b));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, l * 0.5, t));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, t));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0.5), false));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, 0, b));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, l * 0.5, b));
    constr.push(new mxConnectionConstraint(new mxPoint(0, 0), false, null, l, t));

    return constr;
  };

  UmlLifeline.prototype.constraints = null;
  OrShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0, 0.25), false),
    new mxConnectionConstraint(new mxPoint(0, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0, 0.75), false),
    new mxConnectionConstraint(new mxPoint(1, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0.7, 0.1), false),
    new mxConnectionConstraint(new mxPoint(0.7, 0.9), false),
  ];
  XorShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0.175, 0.25), false),
    new mxConnectionConstraint(new mxPoint(0.25, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0.175, 0.75), false),
    new mxConnectionConstraint(new mxPoint(1, 0.5), false),
    new mxConnectionConstraint(new mxPoint(0.7, 0.1), false),
    new mxConnectionConstraint(new mxPoint(0.7, 0.9), false),
  ];
  RequiredInterfaceShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0, 0.5), false),
    new mxConnectionConstraint(new mxPoint(1, 0.5), false),
  ];
  ProvidedRequiredInterfaceShape.prototype.constraints = [
    new mxConnectionConstraint(new mxPoint(0, 0.5), false),
    new mxConnectionConstraint(new mxPoint(1, 0.5), false),
  ];
})();
/**
 * Copyright (c) 2006-2020, JGraph Ltd
 * Copyright (c) 2006-2020, draw.io AG
 *
 * Constructs the actions object for the given UI.
 */
function Actions(editorUi) {
  this.editorUi = editorUi;
  this.actions = new Object();
  this.init();
}

/**
 * Adds the default actions.
 */
Actions.prototype.init = function () {
  var ui = this.editorUi;
  var editor = ui.editor;
  var graph = editor.graph;
  var isGraphEnabled = function () {
    return Action.prototype.isEnabled.apply(this, arguments) && graph.isEnabled();
  };

  // File actions
  this.addAction('new...', function () {
    graph.openLink(ui.getUrl());
  });
  this.addAction('open...', function () {
    window.openNew = true;
    window.openKey = 'open';

    ui.openFile();
  });
  this.addAction('import...', function () {
    window.openNew = false;
    window.openKey = 'import';

    // Closes dialog after open
    window.openFile = new OpenFile(
      mxUtils.bind(this, function () {
        ui.hideDialog();
      })
    );

    window.openFile.setConsumer(
      mxUtils.bind(this, function (xml, filename) {
        try {
          var doc = mxUtils.parseXml(xml);
          editor.graph.setSelectionCells(editor.graph.importGraphModel(doc.documentElement));
        } catch (e) {
          mxUtils.alert(mxResources.get('invalidOrMissingFile') + ': ' + e.message);
        }
      })
    );

    // Removes openFile if dialog is closed
    ui.showDialog(new OpenDialog(this).container, 320, 220, true, true, function () {
      window.openFile = null;
    });
  }).isEnabled = isGraphEnabled;
  this.addAction(
    'save',
    function () {
      ui.saveFile(false);
    },
    null,
    null,
    Editor.ctrlKey + '+S'
  ).isEnabled = isGraphEnabled;
  this.addAction(
    'saveAs...',
    function () {
      ui.saveFile(true);
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+S'
  ).isEnabled = isGraphEnabled;
  this.addAction('export...', function () {
    ui.showDialog(new ExportDialog(ui).container, 300, 296, true, true);
  });
  this.addAction('editDiagram...', function () {
    var dlg = new EditDiagramDialog(ui);
    ui.showDialog(dlg.container, 620, 420, true, false);
    dlg.init();
  });
  this.addAction('pageSetup...', function () {
    ui.showDialog(new PageSetupDialog(ui).container, 320, 220, true, true);
  }).isEnabled = isGraphEnabled;
  this.addAction(
    'print...',
    function () {
      ui.showDialog(new PrintDialog(ui).container, 300, 180, true, true);
    },
    null,
    'sprite-print',
    Editor.ctrlKey + '+P'
  );
  this.addAction('preview', function () {
    mxUtils.show(graph, null, 10, 10);
  });

  // Edit actions
  this.addAction(
    'undo',
    function () {
      ui.undo();
    },
    null,
    'sprite-undo',
    Editor.ctrlKey + '+Z'
  );
  this.addAction(
    'redo',
    function () {
      ui.redo();
    },
    null,
    'sprite-redo',
    !mxClient.IS_WIN ? Editor.ctrlKey + '+Shift+Z' : Editor.ctrlKey + '+Y'
  );
  this.addAction(
    'cut',
    function () {
      mxClipboard.cut(graph);
    },
    null,
    'sprite-cut',
    Editor.ctrlKey + '+X'
  );
  this.addAction(
    'copy',
    function () {
      try {
        mxClipboard.copy(graph);
      } catch (e) {
        ui.handleError(e);
      }
    },
    null,
    'sprite-copy',
    Editor.ctrlKey + '+C'
  );
  this.addAction(
    'paste',
    function () {
      if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent())) {
        mxClipboard.paste(graph);
      }
    },
    false,
    'sprite-paste',
    Editor.ctrlKey + '+V'
  );
  this.addAction('pasteHere', function (evt) {
    if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent())) {
      graph.getModel().beginUpdate();
      try {
        var cells = mxClipboard.paste(graph);

        if (cells != null) {
          var includeEdges = true;

          for (var i = 0; i < cells.length && includeEdges; i++) {
            includeEdges = includeEdges && graph.model.isEdge(cells[i]);
          }

          var t = graph.view.translate;
          var s = graph.view.scale;
          var dx = t.x;
          var dy = t.y;
          var bb = null;

          if (cells.length == 1 && includeEdges) {
            var geo = graph.getCellGeometry(cells[0]);

            if (geo != null) {
              bb = geo.getTerminalPoint(true);
            }
          }

          bb = bb != null ? bb : graph.getBoundingBoxFromGeometry(cells, includeEdges);

          if (bb != null) {
            var x = Math.round(graph.snap(graph.popupMenuHandler.triggerX / s - dx));
            var y = Math.round(graph.snap(graph.popupMenuHandler.triggerY / s - dy));

            graph.cellsMoved(cells, x - bb.x, y - bb.y);
          }
        }
      } finally {
        graph.getModel().endUpdate();
      }
    }
  });

  this.addAction(
    'copySize',
    function (evt) {
      var cell = graph.getSelectionCell();

      if (graph.isEnabled() && cell != null && graph.getModel().isVertex(cell)) {
        var geo = graph.getCellGeometry(cell);

        if (geo != null) {
          ui.copiedSize = new mxRectangle(geo.x, geo.y, geo.width, geo.height);
        }
      }
    },
    null,
    null,
    'Alt+Shift+X'
  );

  this.addAction(
    'pasteSize',
    function (evt) {
      if (graph.isEnabled() && !graph.isSelectionEmpty() && ui.copiedSize != null) {
        graph.getModel().beginUpdate();

        try {
          var cells = graph.getSelectionCells();

          for (var i = 0; i < cells.length; i++) {
            if (graph.getModel().isVertex(cells[i])) {
              var geo = graph.getCellGeometry(cells[i]);

              if (geo != null) {
                geo = geo.clone();
                geo.width = ui.copiedSize.width;
                geo.height = ui.copiedSize.height;

                graph.getModel().setGeometry(cells[i], geo);
              }
            }
          }
        } finally {
          graph.getModel().endUpdate();
        }
      }
    },
    null,
    null,
    'Alt+Shift+V'
  );

  function deleteCells(includeEdges) {
    // Cancels interactive operations
    graph.escape();
    var select = graph.deleteCells(graph.getDeletableCells(graph.getSelectionCells()), includeEdges);

    if (select != null) {
      graph.setSelectionCells(select);
    }
  }

  this.addAction(
    'delete',
    function (evt) {
      deleteCells(evt != null && mxEvent.isControlDown(evt));
    },
    null,
    null,
    'Delete'
  );
  this.addAction('deleteAll', function () {
    if (!graph.isSelectionEmpty()) {
      graph.getModel().beginUpdate();
      try {
        var cells = graph.getSelectionCells();

        for (var i = 0; i < cells.length; i++) {
          graph.cellLabelChanged(cells[i], '');
        }
      } finally {
        graph.getModel().endUpdate();
      }
    }
  });
  this.addAction(
    'deleteLabels',
    function () {
      if (!graph.isSelectionEmpty()) {
        graph.getModel().beginUpdate();
        try {
          var cells = graph.getSelectionCells();

          for (var i = 0; i < cells.length; i++) {
            graph.cellLabelChanged(cells[i], '');
          }
        } finally {
          graph.getModel().endUpdate();
        }
      }
    },
    null,
    null,
    Editor.ctrlKey + '+Delete'
  );
  this.addAction(
    'duplicate',
    function () {
      try {
        graph.setSelectionCells(graph.duplicateCells());
        graph.scrollCellToVisible(graph.getSelectionCell());
      } catch (e) {
        ui.handleError(e);
      }
    },
    null,
    null,
    Editor.ctrlKey + '+D'
  );
  this.put(
    'turn',
    new Action(
      mxResources.get('turn') + ' / ' + mxResources.get('reverse'),
      function (evt) {
        graph.turnShapes(graph.getSelectionCells(), evt != null ? mxEvent.isShiftDown(evt) : false);
      },
      null,
      null,
      Editor.ctrlKey + '+R'
    )
  );
  this.addAction(
    'selectVertices',
    function () {
      graph.selectVertices(null, true);
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+I'
  );
  this.addAction(
    'selectEdges',
    function () {
      graph.selectEdges();
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+E'
  );
  this.addAction(
    'selectAll',
    function () {
      graph.selectAll(null, true);
    },
    null,
    null,
    Editor.ctrlKey + '+A'
  );
  this.addAction(
    'selectNone',
    function () {
      graph.clearSelection();
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+A'
  );
  this.addAction(
    'lockUnlock',
    function () {
      if (!graph.isSelectionEmpty()) {
        graph.getModel().beginUpdate();
        try {
          var defaultValue = graph.isCellMovable(graph.getSelectionCell()) ? 1 : 0;
          graph.toggleCellStyles(mxConstants.STYLE_MOVABLE, defaultValue);
          graph.toggleCellStyles(mxConstants.STYLE_RESIZABLE, defaultValue);
          graph.toggleCellStyles(mxConstants.STYLE_ROTATABLE, defaultValue);
          graph.toggleCellStyles(mxConstants.STYLE_DELETABLE, defaultValue);
          graph.toggleCellStyles(mxConstants.STYLE_EDITABLE, defaultValue);
          graph.toggleCellStyles('connectable', defaultValue);
        } finally {
          graph.getModel().endUpdate();
        }
      }
    },
    null,
    null,
    Editor.ctrlKey + '+L'
  );

  // Navigation actions
  this.addAction(
    'home',
    function () {
      graph.home();
    },
    null,
    null,
    'Shift+Home'
  );
  this.addAction(
    'exitGroup',
    function () {
      graph.exitGroup();
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+Home'
  );
  this.addAction(
    'enterGroup',
    function () {
      graph.enterGroup();
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+End'
  );
  this.addAction(
    'collapse',
    function () {
      graph.foldCells(true);
    },
    null,
    null,
    Editor.ctrlKey + '+Home'
  );
  this.addAction(
    'expand',
    function () {
      graph.foldCells(false);
    },
    null,
    null,
    Editor.ctrlKey + '+End'
  );

  // Arrange actions
  this.addAction(
    'toFront',
    function () {
      graph.orderCells(false);
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+F'
  );
  this.addAction(
    'toBack',
    function () {
      graph.orderCells(true);
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+B'
  );
  this.addAction(
    'group',
    function () {
      if (graph.isEnabled()) {
        var cells = mxUtils.sortCells(graph.getSelectionCells(), true);

        if (cells.length == 1 && !graph.isTable(cells[0]) && !graph.isTableRow(cells[0])) {
          graph.setCellStyles('container', '1');
        } else {
          cells = graph.getCellsForGroup(cells);

          if (cells.length > 1) {
            graph.setSelectionCell(graph.groupCells(null, 0, cells));
          }
        }
      }
    },
    null,
    null,
    Editor.ctrlKey + '+G'
  );
  this.addAction(
    'ungroup',
    function () {
      if (graph.isEnabled()) {
        var cells = graph.getSelectionCells();

        graph.model.beginUpdate();
        try {
          var temp = graph.ungroupCells();

          // Clears container flag for remaining cells
          if (cells != null) {
            for (var i = 0; i < cells.length; i++) {
              if (graph.model.contains(cells[i])) {
                if (graph.model.getChildCount(cells[i]) == 0 && graph.model.isVertex(cells[i])) {
                  graph.setCellStyles('container', '0', [cells[i]]);
                }

                temp.push(cells[i]);
              }
            }
          }
        } finally {
          graph.model.endUpdate();
        }

        graph.setSelectionCells(temp);
      }
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+U'
  );
  this.addAction('removeFromGroup', function () {
    if (graph.isEnabled()) {
      var cells = graph.getSelectionCells();

      // Removes table rows and cells
      if (cells != null) {
        var temp = [];

        for (var i = 0; i < cells.length; i++) {
          if (!graph.isTableRow(cells[i]) && !graph.isTableCell(cells[i])) {
            temp.push(cells[i]);
          }
        }

        graph.removeCellsFromParent(temp);
      }
    }
  });
  // Adds action
  this.addAction(
    'edit',
    function () {
      if (graph.isEnabled()) {
        graph.startEditingAtCell();
      }
    },
    null,
    null,
    'F2/Enter'
  );
  this.addAction(
    'editData...',
    function () {
      var cell = graph.getSelectionCell() || graph.getModel().getRoot();
      ui.showDataDialog(cell);
    },
    null,
    null,
    Editor.ctrlKey + '+M'
  );
  this.addAction(
    'editTooltip...',
    function () {
      if (graph.isEnabled() && !graph.isSelectionEmpty()) {
        var cell = graph.getSelectionCell();
        var tooltip = '';

        if (mxUtils.isNode(cell.value)) {
          var tmp = null;

          if (
            Graph.translateDiagram &&
            Graph.diagramLanguage != null &&
            cell.value.hasAttribute('tooltip_' + Graph.diagramLanguage)
          ) {
            tmp = cell.value.getAttribute('tooltip_' + Graph.diagramLanguage);
          }

          if (tmp == null) {
            tmp = cell.value.getAttribute('tooltip');
          }

          if (tmp != null) {
            tooltip = tmp;
          }
        }

        var dlg = new TextareaDialog(ui, mxResources.get('editTooltip') + ':', tooltip, function (newValue) {
          graph.setTooltipForCell(cell, newValue);
        });
        ui.showDialog(dlg.container, 320, 200, true, true);
        dlg.init();
      }
    },
    null,
    null,
    'Alt+Shift+T'
  );
  this.addAction('openLink', function () {
    var link = graph.getLinkForCell(graph.getSelectionCell());

    if (link != null) {
      graph.openLink(link);
    }
  });
  this.addAction(
    'editLink...',
    function () {
      if (graph.isEnabled() && !graph.isSelectionEmpty()) {
        var cell = graph.getSelectionCell();
        var value = graph.getLinkForCell(cell) || '';

        ui.showLinkDialog(value, mxResources.get('apply'), function (link) {
          link = mxUtils.trim(link);
          graph.setLinkForCell(cell, link.length > 0 ? link : null);
        });
      }
    },
    null,
    null,
    'Alt+Shift+L'
  );
  this.put(
    'insertImage',
    new Action(mxResources.get('image') + '...', function () {
      if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent())) {
        graph.clearSelection();
        ui.actions.get('image').funct();
      }
    })
  ).isEnabled = isGraphEnabled;
  this.put(
    'insertLink',
    new Action(mxResources.get('link') + '...', function () {
      if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent())) {
        ui.showLinkDialog('', mxResources.get('insert'), function (link, docs) {
          link = mxUtils.trim(link);

          if (link.length > 0) {
            var icon = null;
            var title = graph.getLinkTitle(link);

            if (docs != null && docs.length > 0) {
              icon = docs[0].iconUrl;
              title = docs[0].name || docs[0].type;
              title = title.charAt(0).toUpperCase() + title.substring(1);

              if (title.length > 30) {
                title = title.substring(0, 30) + '...';
              }
            }

            var linkCell = new mxCell(
              title,
              new mxGeometry(0, 0, 100, 40),
              'fontColor=#0000EE;fontStyle=4;rounded=1;overflow=hidden;' +
                (icon != null
                  ? 'shape=label;imageWidth=16;imageHeight=16;spacingLeft=26;align=left;image=' + icon
                  : 'spacing=10;')
            );
            linkCell.vertex = true;

            var pt = graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry([linkCell], true));
            linkCell.geometry.x = pt.x;
            linkCell.geometry.y = pt.y;

            graph.setLinkForCell(linkCell, link);
            graph.cellSizeUpdated(linkCell, true);

            graph.getModel().beginUpdate();
            try {
              linkCell = graph.addCell(linkCell);
              graph.fireEvent(new mxEventObject('cellsInserted', 'cells', [linkCell]));
            } finally {
              graph.getModel().endUpdate();
            }

            graph.setSelectionCell(linkCell);
            graph.scrollCellToVisible(graph.getSelectionCell());
          }
        });
      }
    })
  ).isEnabled = isGraphEnabled;
  this.addAction(
    'link...',
    mxUtils.bind(this, function () {
      if (graph.isEnabled()) {
        if (graph.cellEditor.isContentEditing()) {
          var elt = graph.getSelectedElement();
          var link = graph.getParentByName(elt, 'A', graph.cellEditor.textarea);
          var oldValue = '';

          // Workaround for FF returning the outermost selected element after double
          // click on a DOM hierarchy with a link inside (but not as topmost element)
          if (link == null && elt != null && elt.getElementsByTagName != null) {
            // Finds all links in the selected DOM and uses the link
            // where the selection text matches its text content
            var links = elt.getElementsByTagName('a');

            for (var i = 0; i < links.length && link == null; i++) {
              if (links[i].textContent == elt.textContent) {
                link = links[i];
              }
            }
          }

          if (link != null && link.nodeName == 'A') {
            oldValue = link.getAttribute('href') || '';
            graph.selectNode(link);
          }

          var selState = graph.cellEditor.saveSelection();

          ui.showLinkDialog(
            oldValue,
            mxResources.get('apply'),
            mxUtils.bind(this, function (value) {
              graph.cellEditor.restoreSelection(selState);

              if (value != null) {
                graph.insertLink(value);
              }
            })
          );
        } else if (graph.isSelectionEmpty()) {
          this.get('insertLink').funct();
        } else {
          this.get('editLink').funct();
        }
      }
    })
  ).isEnabled = isGraphEnabled;
  this.addAction(
    'autosize',
    function () {
      var cells = graph.getSelectionCells();

      if (cells != null) {
        graph.getModel().beginUpdate();
        try {
          for (var i = 0; i < cells.length; i++) {
            var cell = cells[i];

            if (graph.getModel().getChildCount(cell)) {
              graph.updateGroupBounds([cell], 20);
            } else {
              var state = graph.view.getState(cell);
              var geo = graph.getCellGeometry(cell);

              if (
                graph.getModel().isVertex(cell) &&
                state != null &&
                state.text != null &&
                geo != null &&
                graph.isWrapping(cell)
              ) {
                geo = geo.clone();
                geo.height = state.text.boundingBox.height / graph.view.scale;
                graph.getModel().setGeometry(cell, geo);
              } else {
                graph.updateCellSize(cell);
              }
            }
          }
        } finally {
          graph.getModel().endUpdate();
        }
      }
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+Y'
  );
  this.addAction('formattedText', function () {
    var refState = graph.getView().getState(graph.getSelectionCell());

    if (refState != null) {
      graph.stopEditing();
      var value = refState.style['html'] == '1' ? null : '1';

      graph.getModel().beginUpdate();
      try {
        var cells = graph.getSelectionCells();

        for (var i = 0; i < cells.length; i++) {
          state = graph.getView().getState(cells[i]);

          if (state != null) {
            var html = mxUtils.getValue(state.style, 'html', '0');

            if (html == '1' && value == null) {
              var label = graph.convertValueToString(state.cell);

              if (mxUtils.getValue(state.style, 'nl2Br', '1') != '0') {
                // Removes newlines from HTML and converts breaks to newlines
                // to match the HTML output in plain text
                label = label.replace(/\n/g, '').replace(/<br\s*.?>/g, '\n');
              }

              // Removes HTML tags
              var temp = document.createElement('div');
              temp.innerHTML = graph.sanitizeHtml(label);
              label = mxUtils.extractTextWithWhitespace(temp.childNodes);

              graph.cellLabelChanged(state.cell, label);
              graph.setCellStyles('html', value, [cells[i]]);
            } else if (html == '0' && value == '1') {
              // Converts HTML tags to text
              var label = mxUtils.htmlEntities(graph.convertValueToString(state.cell), false);

              if (mxUtils.getValue(state.style, 'nl2Br', '1') != '0') {
                // Converts newlines in plain text to breaks in HTML
                // to match the plain text output
                label = label.replace(/\n/g, '<br/>');
              }

              graph.cellLabelChanged(state.cell, graph.sanitizeHtml(label));
              graph.setCellStyles('html', value, [cells[i]]);
            }
          }
        }

        ui.fireEvent(
          new mxEventObject('styleChanged', 'keys', ['html'], 'values', [value != null ? value : '0'], 'cells', cells)
        );
      } finally {
        graph.getModel().endUpdate();
      }
    }
  });
  this.addAction('wordWrap', function () {
    var state = graph.getView().getState(graph.getSelectionCell());
    var value = 'wrap';

    graph.stopEditing();

    if (state != null && state.style[mxConstants.STYLE_WHITE_SPACE] == 'wrap') {
      value = null;
    }

    graph.setCellStyles(mxConstants.STYLE_WHITE_SPACE, value);
  });
  this.addAction('rotation', function () {
    var value = '0';
    var state = graph.getView().getState(graph.getSelectionCell());

    if (state != null) {
      value = state.style[mxConstants.STYLE_ROTATION] || value;
    }

    var dlg = new FilenameDialog(
      ui,
      value,
      mxResources.get('apply'),
      function (newValue) {
        if (newValue != null && newValue.length > 0) {
          graph.setCellStyles(mxConstants.STYLE_ROTATION, newValue);
        }
      },
      mxResources.get('enterValue') + ' (' + mxResources.get('rotation') + ' 0-360)'
    );

    ui.showDialog(dlg.container, 375, 80, true, true);
    dlg.init();
  });
  // View actions
  this.addAction(
    'resetView',
    function () {
      graph.zoomTo(1);
      ui.resetScrollbars();
    },
    null,
    null,
    'Home'
  );
  this.addAction(
    'zoomIn',
    function (evt) {
      if (graph.isFastZoomEnabled()) {
        graph.lazyZoom(true, true, ui.buttonZoomDelay);
      } else {
        graph.zoomIn();
      }
    },
    null,
    null,
    Editor.ctrlKey + ' + (Numpad) / Alt+Mousewheel'
  );
  this.addAction(
    'zoomOut',
    function (evt) {
      if (graph.isFastZoomEnabled()) {
        graph.lazyZoom(false, true, ui.buttonZoomDelay);
      } else {
        graph.zoomOut();
      }
    },
    null,
    null,
    Editor.ctrlKey + ' - (Numpad) / Alt+Mousewheel'
  );
  this.addAction(
    'fitWindow',
    function () {
      var bounds = graph.isSelectionEmpty() ? graph.getGraphBounds() : graph.getBoundingBox(graph.getSelectionCells());
      var t = graph.view.translate;
      var s = graph.view.scale;

      bounds.x = bounds.x / s - t.x;
      bounds.y = bounds.y / s - t.y;
      bounds.width /= s;
      bounds.height /= s;

      if (graph.backgroundImage != null) {
        bounds.add(new mxRectangle(0, 0, graph.backgroundImage.width, graph.backgroundImage.height));
      }

      if (bounds.width == 0 || bounds.height == 0) {
        graph.zoomTo(1);
        ui.resetScrollbars();
      } else {
        graph.fitWindow(bounds);
      }
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+H'
  );
  this.addAction(
    'fitPage',
    mxUtils.bind(this, function () {
      if (!graph.pageVisible) {
        this.get('pageView').funct();
      }

      var fmt = graph.pageFormat;
      var ps = graph.pageScale;
      var cw = graph.container.clientWidth - 10;
      var ch = graph.container.clientHeight - 10;
      var scale = Math.floor(20 * Math.min(cw / fmt.width / ps, ch / fmt.height / ps)) / 20;
      graph.zoomTo(scale);

      if (mxUtils.hasScrollbars(graph.container)) {
        var pad = graph.getPagePadding();
        graph.container.scrollTop = pad.y * graph.view.scale - 1;
        graph.container.scrollLeft =
          Math.min(pad.x * graph.view.scale, (graph.container.scrollWidth - graph.container.clientWidth) / 2) - 1;
      }
    }),
    null,
    null,
    Editor.ctrlKey + '+J'
  );
  this.addAction(
    'fitTwoPages',
    mxUtils.bind(this, function () {
      if (!graph.pageVisible) {
        this.get('pageView').funct();
      }

      var fmt = graph.pageFormat;
      var ps = graph.pageScale;
      var cw = graph.container.clientWidth - 10;
      var ch = graph.container.clientHeight - 10;

      var scale = Math.floor(20 * Math.min(cw / (2 * fmt.width) / ps, ch / fmt.height / ps)) / 20;
      graph.zoomTo(scale);

      if (mxUtils.hasScrollbars(graph.container)) {
        var pad = graph.getPagePadding();
        graph.container.scrollTop = Math.min(pad.y, (graph.container.scrollHeight - graph.container.clientHeight) / 2);
        graph.container.scrollLeft = Math.min(pad.x, (graph.container.scrollWidth - graph.container.clientWidth) / 2);
      }
    }),
    null,
    null,
    Editor.ctrlKey + '+Shift+J'
  );
  this.addAction(
    'fitPageWidth',
    mxUtils.bind(this, function () {
      if (!graph.pageVisible) {
        this.get('pageView').funct();
      }

      var fmt = graph.pageFormat;
      var ps = graph.pageScale;
      var cw = graph.container.clientWidth - 10;

      var scale = Math.floor((20 * cw) / fmt.width / ps) / 20;
      graph.zoomTo(scale);

      if (mxUtils.hasScrollbars(graph.container)) {
        var pad = graph.getPagePadding();
        graph.container.scrollLeft = Math.min(
          pad.x * graph.view.scale,
          (graph.container.scrollWidth - graph.container.clientWidth) / 2
        );
      }
    })
  );
  this.put(
    'customZoom',
    new Action(
      mxResources.get('custom') + '...',
      mxUtils.bind(this, function () {
        var dlg = new FilenameDialog(
          this.editorUi,
          parseInt(graph.getView().getScale() * 100),
          mxResources.get('apply'),
          mxUtils.bind(this, function (newValue) {
            var val = parseInt(newValue);

            if (!isNaN(val) && val > 0) {
              graph.zoomTo(val / 100);
            }
          }),
          mxResources.get('zoom') + ' (%)'
        );
        this.editorUi.showDialog(dlg.container, 300, 80, true, true);
        dlg.init();
      }),
      null,
      null,
      Editor.ctrlKey + '+0'
    )
  );
  this.addAction(
    'pageScale...',
    mxUtils.bind(this, function () {
      var dlg = new FilenameDialog(
        this.editorUi,
        parseInt(graph.pageScale * 100),
        mxResources.get('apply'),
        mxUtils.bind(this, function (newValue) {
          var val = parseInt(newValue);

          if (!isNaN(val) && val > 0) {
            var change = new ChangePageSetup(ui, null, null, null, val / 100);
            change.ignoreColor = true;
            change.ignoreImage = true;

            graph.model.execute(change);
          }
        }),
        mxResources.get('pageScale') + ' (%)'
      );
      this.editorUi.showDialog(dlg.container, 300, 80, true, true);
      dlg.init();
    })
  );

  // Option actions
  var action = null;
  action = this.addAction(
    'grid',
    function () {
      graph.setGridEnabled(!graph.isGridEnabled());
      ui.fireEvent(new mxEventObject('gridEnabledChanged'));
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+G'
  );
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.isGridEnabled();
  });
  action.setEnabled(false);

  action = this.addAction('guides', function () {
    graph.graphHandler.guidesEnabled = !graph.graphHandler.guidesEnabled;
    ui.fireEvent(new mxEventObject('guidesEnabledChanged'));
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.graphHandler.guidesEnabled;
  });
  action.setEnabled(false);

  action = this.addAction('tooltips', function () {
    graph.tooltipHandler.setEnabled(!graph.tooltipHandler.isEnabled());
    ui.fireEvent(new mxEventObject('tooltipsEnabledChanged'));
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.tooltipHandler.isEnabled();
  });

  action = this.addAction('collapseExpand', function () {
    var change = new ChangePageSetup(ui);
    change.ignoreColor = true;
    change.ignoreImage = true;
    change.foldingEnabled = !graph.foldingEnabled;

    graph.model.execute(change);
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.foldingEnabled;
  });
  action.isEnabled = isGraphEnabled;
  action = this.addAction('scrollbars', function () {
    ui.setScrollbars(!ui.hasScrollbars());
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.scrollbars;
  });
  action = this.addAction(
    'pageView',
    mxUtils.bind(this, function () {
      ui.setPageVisible(!graph.pageVisible);
    })
  );
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.pageVisible;
  });
  action = this.addAction(
    'connectionArrows',
    function () {
      graph.connectionArrowsEnabled = !graph.connectionArrowsEnabled;
      ui.fireEvent(new mxEventObject('connectionArrowsChanged'));
    },
    null,
    null,
    'Alt+Shift+A'
  );
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.connectionArrowsEnabled;
  });
  action = this.addAction(
    'connectionPoints',
    function () {
      graph.setConnectable(!graph.connectionHandler.isEnabled());
      ui.fireEvent(new mxEventObject('connectionPointsChanged'));
    },
    null,
    null,
    'Alt+Shift+P'
  );
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.connectionHandler.isEnabled();
  });
  action = this.addAction('copyConnect', function () {
    graph.connectionHandler.setCreateTarget(!graph.connectionHandler.isCreateTarget());
    ui.fireEvent(new mxEventObject('copyConnectChanged'));
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return graph.connectionHandler.isCreateTarget();
  });
  action.isEnabled = isGraphEnabled;
  action = this.addAction('autosave', function () {
    ui.editor.setAutosave(!ui.editor.autosave);
  });
  action.setToggleAction(true);
  action.setSelectedCallback(function () {
    return ui.editor.autosave;
  });
  action.isEnabled = isGraphEnabled;
  action.visible = false;

  // Help actions
  this.addAction('help', function () {
    var ext = '';

    if (mxResources.isLanguageSupported(mxClient.language)) {
      ext = '_' + mxClient.language;
    }

    graph.openLink(RESOURCES_PATH + '/help' + ext + '.html');
  });

  var showingAbout = false;

  this.put(
    'about',
    new Action(mxResources.get('about') + ' Graph Editor...', function () {
      if (!showingAbout) {
        ui.showDialog(new AboutDialog(ui).container, 320, 280, true, true, function () {
          showingAbout = false;
        });

        showingAbout = true;
      }
    })
  );

  // Font style actions
  var toggleFontStyle = mxUtils.bind(this, function (key, style, fn, shortcut) {
    return this.addAction(
      key,
      function () {
        if (fn != null && graph.cellEditor.isContentEditing()) {
          fn();
        } else {
          graph.stopEditing(false);

          graph.getModel().beginUpdate();
          try {
            var cells = graph.getSelectionCells();
            graph.toggleCellStyleFlags(mxConstants.STYLE_FONTSTYLE, style, cells);

            // Removes bold and italic tags and CSS styles inside labels
            if ((style & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) {
              graph.updateLabelElements(graph.getSelectionCells(), function (elt) {
                elt.style.fontWeight = null;

                if (elt.nodeName == 'B') {
                  graph.replaceElement(elt);
                }
              });
            } else if ((style & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) {
              graph.updateLabelElements(graph.getSelectionCells(), function (elt) {
                elt.style.fontStyle = null;

                if (elt.nodeName == 'I') {
                  graph.replaceElement(elt);
                }
              });
            } else if ((style & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) {
              graph.updateLabelElements(graph.getSelectionCells(), function (elt) {
                elt.style.textDecoration = null;

                if (elt.nodeName == 'U') {
                  graph.replaceElement(elt);
                }
              });
            }

            for (var i = 0; i < cells.length; i++) {
              if (graph.model.getChildCount(cells[i]) == 0) {
                graph.autoSizeCell(cells[i], false);
              }
            }
          } finally {
            graph.getModel().endUpdate();
          }
        }
      },
      null,
      null,
      shortcut
    );
  });

  toggleFontStyle(
    'bold',
    mxConstants.FONT_BOLD,
    function () {
      document.execCommand('bold', false, null);
    },
    Editor.ctrlKey + '+B'
  );
  toggleFontStyle(
    'italic',
    mxConstants.FONT_ITALIC,
    function () {
      document.execCommand('italic', false, null);
    },
    Editor.ctrlKey + '+I'
  );
  toggleFontStyle(
    'underline',
    mxConstants.FONT_UNDERLINE,
    function () {
      document.execCommand('underline', false, null);
    },
    Editor.ctrlKey + '+U'
  );

  // Color actions
  this.addAction('fontColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_FONTCOLOR, 'forecolor', '000000');
  });
  this.addAction('strokeColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_STROKECOLOR);
  });
  this.addAction('fillColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_FILLCOLOR);
  });
  this.addAction('gradientColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_GRADIENTCOLOR);
  });
  this.addAction('backgroundColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, 'backcolor');
  });
  this.addAction('borderColor...', function () {
    ui.menus.pickColor(mxConstants.STYLE_LABEL_BORDERCOLOR);
  });

  // Format actions
  this.addAction('vertical', function () {
    ui.menus.toggleStyle(mxConstants.STYLE_HORIZONTAL, true);
  });
  this.addAction('shadow', function () {
    ui.menus.toggleStyle(mxConstants.STYLE_SHADOW);
  });
  this.addAction('solid', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_DASHED, null);
      graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, null);
      ui.fireEvent(
        new mxEventObject(
          'styleChanged',
          'keys',
          [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
          'values',
          [null, null],
          'cells',
          graph.getSelectionCells()
        )
      );
    } finally {
      graph.getModel().endUpdate();
    }
  });
  this.addAction('dashed', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_DASHED, '1');
      graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, null);
      ui.fireEvent(
        new mxEventObject(
          'styleChanged',
          'keys',
          [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
          'values',
          ['1', null],
          'cells',
          graph.getSelectionCells()
        )
      );
    } finally {
      graph.getModel().endUpdate();
    }
  });
  this.addAction('dotted', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_DASHED, '1');
      graph.setCellStyles(mxConstants.STYLE_DASH_PATTERN, '1 4');
      ui.fireEvent(
        new mxEventObject(
          'styleChanged',
          'keys',
          [mxConstants.STYLE_DASHED, mxConstants.STYLE_DASH_PATTERN],
          'values',
          ['1', '1 4'],
          'cells',
          graph.getSelectionCells()
        )
      );
    } finally {
      graph.getModel().endUpdate();
    }
  });
  this.addAction('sharp', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_ROUNDED, '0');
      graph.setCellStyles(mxConstants.STYLE_CURVED, '0');
      ui.fireEvent(
        new mxEventObject(
          'styleChanged',
          'keys',
          [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
          'values',
          ['0', '0'],
          'cells',
          graph.getSelectionCells()
        )
      );
    } finally {
      graph.getModel().endUpdate();
    }
  });
  this.addAction('rounded', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_ROUNDED, '1');
      graph.setCellStyles(mxConstants.STYLE_CURVED, '0');
      ui.fireEvent(
        new mxEventObject(
          'styleChanged',
          'keys',
          [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
          'values',
          ['1', '0'],
          'cells',
          graph.getSelectionCells()
        )
      );
    } finally {
      graph.getModel().endUpdate();
    }
  });
  this.addAction('toggleRounded', function () {
    if (!graph.isSelectionEmpty() && graph.isEnabled()) {
      graph.getModel().beginUpdate();
      try {
        var cells = graph.getSelectionCells();
        var style = graph.getCurrentCellStyle(cells[0]);
        var value = mxUtils.getValue(style, mxConstants.STYLE_ROUNDED, '0') == '1' ? '0' : '1';

        graph.setCellStyles(mxConstants.STYLE_ROUNDED, value);
        graph.setCellStyles(mxConstants.STYLE_CURVED, null);
        ui.fireEvent(
          new mxEventObject(
            'styleChanged',
            'keys',
            [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
            'values',
            [value, '0'],
            'cells',
            graph.getSelectionCells()
          )
        );
      } finally {
        graph.getModel().endUpdate();
      }
    }
  });
  this.addAction('curved', function () {
    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(mxConstants.STYLE_ROUNDED, '0');
      graph.setCellStyles(mxConstants.STYLE_CURVED, '1');
      ui.fireEvent(
        new mxEventObject(
          'styleChanged',
          'keys',
          [mxConstants.STYLE_ROUNDED, mxConstants.STYLE_CURVED],
          'values',
          ['0', '1'],
          'cells',
          graph.getSelectionCells()
        )
      );
    } finally {
      graph.getModel().endUpdate();
    }
  });
  this.addAction('collapsible', function () {
    var state = graph.view.getState(graph.getSelectionCell());
    var value = '1';

    if (state != null && graph.getFoldingImage(state) != null) {
      value = '0';
    }

    graph.setCellStyles('collapsible', value);
    ui.fireEvent(
      new mxEventObject('styleChanged', 'keys', ['collapsible'], 'values', [value], 'cells', graph.getSelectionCells())
    );
  });
  this.addAction(
    'editStyle...',
    mxUtils.bind(this, function () {
      var cells = graph.getSelectionCells();

      if (cells != null && cells.length > 0) {
        var model = graph.getModel();

        var dlg = new TextareaDialog(
          this.editorUi,
          mxResources.get('editStyle') + ':',
          model.getStyle(cells[0]) || '',
          function (newValue) {
            if (newValue != null) {
              graph.setCellStyle(mxUtils.trim(newValue), cells);
            }
          },
          null,
          null,
          400,
          220
        );
        this.editorUi.showDialog(dlg.container, 420, 300, true, true);
        dlg.init();
      }
    }),
    null,
    null,
    Editor.ctrlKey + '+E'
  );
  this.addAction(
    'setAsDefaultStyle',
    function () {
      if (graph.isEnabled() && !graph.isSelectionEmpty()) {
        ui.setDefaultStyle(graph.getSelectionCell());
      }
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+D'
  );
  this.addAction(
    'clearDefaultStyle',
    function () {
      if (graph.isEnabled()) {
        ui.clearDefaultStyle();
      }
    },
    null,
    null,
    Editor.ctrlKey + '+Shift+R'
  );
  this.addAction('addWaypoint', function () {
    var cell = graph.getSelectionCell();

    if (cell != null && graph.getModel().isEdge(cell)) {
      var handler = editor.graph.selectionCellsHandler.getHandler(cell);

      if (handler instanceof mxEdgeHandler) {
        var t = graph.view.translate;
        var s = graph.view.scale;
        var dx = t.x;
        var dy = t.y;

        var parent = graph.getModel().getParent(cell);
        var pgeo = graph.getCellGeometry(parent);

        while (graph.getModel().isVertex(parent) && pgeo != null) {
          dx += pgeo.x;
          dy += pgeo.y;

          parent = graph.getModel().getParent(parent);
          pgeo = graph.getCellGeometry(parent);
        }

        var x = Math.round(graph.snap(graph.popupMenuHandler.triggerX / s - dx));
        var y = Math.round(graph.snap(graph.popupMenuHandler.triggerY / s - dy));

        handler.addPointAt(handler.state, x, y);
      }
    }
  });
  this.addAction('removeWaypoint', function () {
    // TODO: Action should run with "this" set to action
    var rmWaypointAction = ui.actions.get('removeWaypoint');

    if (rmWaypointAction.handler != null) {
      // NOTE: Popupevent handled and action updated in Menus.createPopupMenu
      rmWaypointAction.handler.removePoint(rmWaypointAction.handler.state, rmWaypointAction.index);
    }
  });
  this.addAction(
    'clearWaypoints',
    function () {
      var cells = graph.getSelectionCells();

      if (cells != null) {
        cells = graph.addAllEdges(cells);

        graph.getModel().beginUpdate();
        try {
          for (var i = 0; i < cells.length; i++) {
            var cell = cells[i];

            if (graph.getModel().isEdge(cell)) {
              var geo = graph.getCellGeometry(cell);

              if (geo != null) {
                geo = geo.clone();
                geo.points = null;
                graph.getModel().setGeometry(cell, geo);
              }
            }
          }
        } finally {
          graph.getModel().endUpdate();
        }
      }
    },
    null,
    null,
    'Alt+Shift+C'
  );
  action = this.addAction(
    'subscript',
    mxUtils.bind(this, function () {
      if (graph.cellEditor.isContentEditing()) {
        document.execCommand('subscript', false, null);
      }
    }),
    null,
    null,
    Editor.ctrlKey + '+,'
  );
  action = this.addAction(
    'superscript',
    mxUtils.bind(this, function () {
      if (graph.cellEditor.isContentEditing()) {
        document.execCommand('superscript', false, null);
      }
    }),
    null,
    null,
    Editor.ctrlKey + '+.'
  );
  action = this.addAction(
    'indent',
    mxUtils.bind(this, function () {
      // NOTE: Alt+Tab for outdent implemented via special code in
      // keyHandler.getFunction in EditorUi.js. Ctrl+Tab is reserved.
      if (graph.cellEditor.isContentEditing()) {
        document.execCommand('indent', false, null);
      }
    }),
    null,
    null,
    'Shift+Tab'
  );
  this.addAction('image...', function () {
    if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent())) {
      var title = mxResources.get('image') + ' (' + mxResources.get('url') + '):';
      var state = graph.getView().getState(graph.getSelectionCell());
      var value = '';

      if (state != null) {
        value = state.style[mxConstants.STYLE_IMAGE] || value;
      }

      var selectionState = graph.cellEditor.saveSelection();

      ui.showImageDialog(
        title,
        value,
        function (newValue, w, h) {
          // Inserts image into HTML text
          if (graph.cellEditor.isContentEditing()) {
            graph.cellEditor.restoreSelection(selectionState);
            graph.insertImage(newValue, w, h);
          } else {
            var cells = graph.getSelectionCells();

            if (newValue != null && (newValue.length > 0 || cells.length > 0)) {
              var select = null;

              graph.getModel().beginUpdate();
              try {
                // Inserts new cell if no cell is selected
                if (cells.length == 0) {
                  cells = [
                    graph.insertVertex(
                      graph.getDefaultParent(),
                      null,
                      '',
                      0,
                      0,
                      w,
                      h,
                      'shape=image;imageAspect=0;aspect=fixed;verticalLabelPosition=bottom;verticalAlign=top;'
                    ),
                  ];
                  var pt = graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry(cells, true));
                  cells[0].geometry.x = pt.x;
                  cells[0].geometry.y = pt.y;

                  select = cells;
                  graph.fireEvent(new mxEventObject('cellsInserted', 'cells', select));
                }

                graph.setCellStyles(mxConstants.STYLE_IMAGE, newValue.length > 0 ? newValue : null, cells);

                // Sets shape only if not already shape with image (label or image)
                var style = graph.getCurrentCellStyle(cells[0]);

                if (style[mxConstants.STYLE_SHAPE] != 'image' && style[mxConstants.STYLE_SHAPE] != 'label') {
                  graph.setCellStyles(mxConstants.STYLE_SHAPE, 'image', cells);
                } else if (newValue.length == 0) {
                  graph.setCellStyles(mxConstants.STYLE_SHAPE, null, cells);
                }

                if (graph.getSelectionCount() == 1) {
                  if (w != null && h != null) {
                    var cell = cells[0];
                    var geo = graph.getModel().getGeometry(cell);

                    if (geo != null) {
                      geo = geo.clone();
                      geo.width = w;
                      geo.height = h;
                      graph.getModel().setGeometry(cell, geo);
                    }
                  }
                }
              } finally {
                graph.getModel().endUpdate();
              }

              if (select != null) {
                graph.setSelectionCells(select);
                graph.scrollCellToVisible(select[0]);
              }
            }
          }
        },
        graph.cellEditor.isContentEditing(),
        !graph.cellEditor.isContentEditing()
      );
    }
  }).isEnabled = isGraphEnabled;
  action = this.addAction(
    'layers',
    mxUtils.bind(this, function () {
      if (this.layersWindow == null) {
        // LATER: Check outline window for initial placement
        this.layersWindow = new LayersWindow(ui, document.body.offsetWidth - 280, 120, 220, 196);
        this.layersWindow.window.addListener('show', function () {
          ui.fireEvent(new mxEventObject('layers'));
        });
        this.layersWindow.window.addListener('hide', function () {
          ui.fireEvent(new mxEventObject('layers'));
        });
        this.layersWindow.window.setVisible(true);
        ui.fireEvent(new mxEventObject('layers'));

        this.layersWindow.init();
      } else {
        this.layersWindow.window.setVisible(!this.layersWindow.window.isVisible());
      }
    }),
    null,
    null,
    Editor.ctrlKey + '+Shift+L'
  );
  action.setToggleAction(true);
  action.setSelectedCallback(
    mxUtils.bind(this, function () {
      return this.layersWindow != null && this.layersWindow.window.isVisible();
    })
  );
  action = this.addAction(
    'formatPanel',
    mxUtils.bind(this, function () {
      ui.toggleFormatPanel();
    }),
    null,
    null,
    Editor.ctrlKey + '+Shift+P'
  );
  action.setToggleAction(true);
  action.setSelectedCallback(
    mxUtils.bind(this, function () {
      return ui.formatWidth > 0;
    })
  );
  action = this.addAction(
    'outline',
    mxUtils.bind(this, function () {
      if (this.outlineWindow == null) {
        // LATER: Check layers window for initial placement
        this.outlineWindow = new OutlineWindow(ui, document.body.offsetWidth - 260, 100, 180, 180);
        this.outlineWindow.window.addListener('show', function () {
          ui.fireEvent(new mxEventObject('outline'));
        });
        this.outlineWindow.window.addListener('hide', function () {
          ui.fireEvent(new mxEventObject('outline'));
        });
        this.outlineWindow.window.setVisible(true);
        ui.fireEvent(new mxEventObject('outline'));
      } else {
        this.outlineWindow.window.setVisible(!this.outlineWindow.window.isVisible());
      }
    }),
    null,
    null,
    Editor.ctrlKey + '+Shift+O'
  );

  action.setToggleAction(true);
  action.setSelectedCallback(
    mxUtils.bind(this, function () {
      return this.outlineWindow != null && this.outlineWindow.window.isVisible();
    })
  );
};

/**
 * Registers the given action under the given name.
 */
Actions.prototype.addAction = function (key, funct, enabled, iconCls, shortcut) {
  var title;

  if (key.substring(key.length - 3) == '...') {
    key = key.substring(0, key.length - 3);
    title = mxResources.get(key) + '...';
  } else {
    title = mxResources.get(key);
  }

  return this.put(key, new Action(title, funct, enabled, iconCls, shortcut));
};

/**
 * Registers the given action under the given name.
 */
Actions.prototype.put = function (name, action) {
  this.actions[name] = action;

  return action;
};

/**
 * Returns the action for the given name or null if no such action exists.
 */
Actions.prototype.get = function (name) {
  return this.actions[name];
};

/**
 * Constructs a new action for the given parameters.
 */
function Action(label, funct, enabled, iconCls, shortcut) {
  mxEventSource.call(this);
  this.label = label;
  this.funct = this.createFunction(funct);
  this.enabled = enabled != null ? enabled : true;
  this.iconCls = iconCls;
  this.shortcut = shortcut;
  this.visible = true;
}

// Action inherits from mxEventSource
mxUtils.extend(Action, mxEventSource);

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Action.prototype.createFunction = function (funct) {
  return funct;
};

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Action.prototype.setEnabled = function (value) {
  if (this.enabled != value) {
    this.enabled = value;
    this.fireEvent(new mxEventObject('stateChanged'));
  }
};

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Action.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Action.prototype.setToggleAction = function (value) {
  this.toggleAction = value;
};

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Action.prototype.setSelectedCallback = function (funct) {
  this.selectedCallback = funct;
};

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Action.prototype.isSelected = function () {
  return this.selectedCallback();
};
/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
/**
 * Constructs a new graph editor
 */
Menus = function (editorUi) {
  this.editorUi = editorUi;
  this.menus = new Object();
  this.init();

  // Pre-fetches checkmark image
  if (!mxClient.IS_SVG) {
    new Image().src = this.checkmarkImage;
  }
};

/**
 * Sets the default font family.
 */
Menus.prototype.defaultFont = 'Helvetica';

/**
 * Sets the default font size.
 */
Menus.prototype.defaultFontSize = '12';

/**
 * Sets the default font size.
 */
Menus.prototype.defaultMenuItems = ['edit', 'view', 'arrange', 'extras'];

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.defaultFonts = [
  'Helvetica',
  'Verdana',
  'Times New Roman',
  'Garamond',
  'Comic Sans MS',
  'Courier New',
  'Georgia',
  'Lucida Console',
  'Tahoma',
];

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.init = function () {
  var graph = this.editorUi.editor.graph;
  var isGraphEnabled = mxUtils.bind(graph, graph.isEnabled);

  this.customFonts = [];
  this.customFontSizes = [];

  this.put(
    'fontFamily',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        var addItem = mxUtils.bind(this, function (fontname) {
          var tr = this.styleChange(
            menu,
            fontname,
            [mxConstants.STYLE_FONTFAMILY],
            [fontname],
            null,
            parent,
            function () {
              document.execCommand('fontname', false, fontname);
            },
            function () {
              graph.updateLabelElements(graph.getSelectionCells(), function (elt) {
                elt.removeAttribute('face');
                elt.style.fontFamily = null;

                if (elt.nodeName == 'PRE') {
                  graph.replaceElement(elt, 'div');
                }
              });
            }
          );
          tr.firstChild.nextSibling.style.fontFamily = fontname;
        });

        for (var i = 0; i < this.defaultFonts.length; i++) {
          addItem(this.defaultFonts[i]);
        }

        menu.addSeparator(parent);

        if (this.customFonts.length > 0) {
          for (var i = 0; i < this.customFonts.length; i++) {
            addItem(this.customFonts[i]);
          }

          menu.addSeparator(parent);

          menu.addItem(
            mxResources.get('reset'),
            null,
            mxUtils.bind(this, function () {
              this.customFonts = [];
              this.editorUi.fireEvent(new mxEventObject('customFontsChanged'));
            }),
            parent
          );

          menu.addSeparator(parent);
        }

        this.promptChange(
          menu,
          mxResources.get('custom') + '...',
          '',
          mxConstants.DEFAULT_FONTFAMILY,
          mxConstants.STYLE_FONTFAMILY,
          parent,
          true,
          mxUtils.bind(this, function (newValue) {
            if (mxUtils.indexOf(this.customFonts, newValue) < 0) {
              this.customFonts.push(newValue);
              this.editorUi.fireEvent(new mxEventObject('customFontsChanged'));
            }
          })
        );
      })
    )
  );
  this.put(
    'formatBlock',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        function addItem(label, tag) {
          return menu.addItem(
            label,
            null,
            mxUtils.bind(this, function () {
              // TODO: Check if visible
              if (graph.cellEditor.textarea != null) {
                graph.cellEditor.textarea.focus();
                document.execCommand('formatBlock', false, '<' + tag + '>');
              }
            }),
            parent
          );
        }

        addItem(mxResources.get('normal'), 'p');

        addItem('', 'h1').firstChild.nextSibling.innerHTML =
          '<h1 style="margin:0px;">' + mxResources.get('heading') + ' 1</h1>';
        addItem('', 'h2').firstChild.nextSibling.innerHTML =
          '<h2 style="margin:0px;">' + mxResources.get('heading') + ' 2</h2>';
        addItem('', 'h3').firstChild.nextSibling.innerHTML =
          '<h3 style="margin:0px;">' + mxResources.get('heading') + ' 3</h3>';
        addItem('', 'h4').firstChild.nextSibling.innerHTML =
          '<h4 style="margin:0px;">' + mxResources.get('heading') + ' 4</h4>';
        addItem('', 'h5').firstChild.nextSibling.innerHTML =
          '<h5 style="margin:0px;">' + mxResources.get('heading') + ' 5</h5>';
        addItem('', 'h6').firstChild.nextSibling.innerHTML =
          '<h6 style="margin:0px;">' + mxResources.get('heading') + ' 6</h6>';

        addItem('', 'pre').firstChild.nextSibling.innerHTML =
          '<pre style="margin:0px;">' + mxResources.get('formatted') + '</pre>';
        addItem('', 'blockquote').firstChild.nextSibling.innerHTML =
          '<blockquote style="margin-top:0px;margin-bottom:0px;">' + mxResources.get('blockquote') + '</blockquote>';
      })
    )
  );
  this.put(
    'fontSize',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        var sizes = [6, 8, 9, 10, 11, 12, 14, 18, 24, 36, 48, 72];

        var addItem = mxUtils.bind(this, function (fontsize) {
          this.styleChange(menu, fontsize, [mxConstants.STYLE_FONTSIZE], [fontsize], null, parent, function () {
            if (graph.cellEditor.textarea != null) {
              // Creates an element with arbitrary size 3
              document.execCommand('fontSize', false, '3');

              // Changes the css font size of the first font element inside the in-place editor with size 3
              // hopefully the above element that we've just created. LATER: Check for new element using
              // previous result of getElementsByTagName (see other actions)
              var elts = graph.cellEditor.textarea.getElementsByTagName('font');

              for (var i = 0; i < elts.length; i++) {
                if (elts[i].getAttribute('size') == '3') {
                  elts[i].removeAttribute('size');
                  elts[i].style.fontSize = fontsize + 'px';

                  break;
                }
              }
            }
          });
        });

        for (var i = 0; i < sizes.length; i++) {
          addItem(sizes[i]);
        }

        menu.addSeparator(parent);

        if (this.customFontSizes.length > 0) {
          for (var i = 0; i < this.customFontSizes.length; i++) {
            addItem(this.customFontSizes[i]);
          }

          menu.addSeparator(parent);

          menu.addItem(
            mxResources.get('reset'),
            null,
            mxUtils.bind(this, function () {
              this.customFontSizes = [];
            }),
            parent
          );

          menu.addSeparator(parent);
        }

        this.promptChange(
          menu,
          mxResources.get('custom') + '...',
          '(pt)',
          '12',
          mxConstants.STYLE_FONTSIZE,
          parent,
          true,
          mxUtils.bind(this, function (newValue) {
            this.customFontSizes.push(newValue);
          })
        );
      })
    )
  );
  this.put(
    'direction',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        menu.addItem(
          mxResources.get('flipH'),
          null,
          function () {
            graph.toggleCellStyles(mxConstants.STYLE_FLIPH, false);
          },
          parent
        );
        menu.addItem(
          mxResources.get('flipV'),
          null,
          function () {
            graph.toggleCellStyles(mxConstants.STYLE_FLIPV, false);
          },
          parent
        );
        this.addMenuItems(menu, ['-', 'rotation'], parent);
      })
    )
  );
  this.put(
    'align',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        menu.addItem(
          mxResources.get('leftAlign'),
          null,
          function () {
            graph.alignCells(mxConstants.ALIGN_LEFT);
          },
          parent
        );
        menu.addItem(
          mxResources.get('center'),
          null,
          function () {
            graph.alignCells(mxConstants.ALIGN_CENTER);
          },
          parent
        );
        menu.addItem(
          mxResources.get('rightAlign'),
          null,
          function () {
            graph.alignCells(mxConstants.ALIGN_RIGHT);
          },
          parent
        );
        menu.addSeparator(parent);
        menu.addItem(
          mxResources.get('topAlign'),
          null,
          function () {
            graph.alignCells(mxConstants.ALIGN_TOP);
          },
          parent
        );
        menu.addItem(
          mxResources.get('middle'),
          null,
          function () {
            graph.alignCells(mxConstants.ALIGN_MIDDLE);
          },
          parent
        );
        menu.addItem(
          mxResources.get('bottomAlign'),
          null,
          function () {
            graph.alignCells(mxConstants.ALIGN_BOTTOM);
          },
          parent
        );
      })
    )
  );
  this.put(
    'distribute',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        menu.addItem(
          mxResources.get('horizontal'),
          null,
          function () {
            graph.distributeCells(true);
          },
          parent
        );
        menu.addItem(
          mxResources.get('vertical'),
          null,
          function () {
            graph.distributeCells(false);
          },
          parent
        );
      })
    )
  );
  this.put(
    'layout',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        var promptSpacing = mxUtils.bind(this, function (defaultValue, fn) {
          var dlg = new FilenameDialog(
            this.editorUi,
            defaultValue,
            mxResources.get('apply'),
            function (newValue) {
              fn(parseFloat(newValue));
            },
            mxResources.get('spacing')
          );
          this.editorUi.showDialog(dlg.container, 300, 80, true, true);
          dlg.init();
        });

        menu.addItem(
          mxResources.get('horizontalFlow'),
          null,
          mxUtils.bind(this, function () {
            var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_WEST);

            this.editorUi.executeLayout(function () {
              var selectionCells = graph.getSelectionCells();
              layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
            }, true);
          }),
          parent
        );
        menu.addItem(
          mxResources.get('verticalFlow'),
          null,
          mxUtils.bind(this, function () {
            var layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_NORTH);

            this.editorUi.executeLayout(function () {
              var selectionCells = graph.getSelectionCells();
              layout.execute(graph.getDefaultParent(), selectionCells.length == 0 ? null : selectionCells);
            }, true);
          }),
          parent
        );
        menu.addSeparator(parent);
        menu.addItem(
          mxResources.get('horizontalTree'),
          null,
          mxUtils.bind(this, function () {
            var tmp = graph.getSelectionCell();
            var roots = null;

            if (tmp == null || graph.getModel().getChildCount(tmp) == 0) {
              if (graph.getModel().getEdgeCount(tmp) == 0) {
                roots = graph.findTreeRoots(graph.getDefaultParent());
              }
            } else {
              roots = graph.findTreeRoots(tmp);
            }

            if (roots != null && roots.length > 0) {
              tmp = roots[0];
            }

            if (tmp != null) {
              var layout = new mxCompactTreeLayout(graph, true);
              layout.edgeRouting = false;
              layout.levelDistance = 30;

              promptSpacing(
                layout.levelDistance,
                mxUtils.bind(this, function (newValue) {
                  layout.levelDistance = newValue;

                  this.editorUi.executeLayout(function () {
                    layout.execute(graph.getDefaultParent(), tmp);
                  }, true);
                })
              );
            }
          }),
          parent
        );
        menu.addItem(
          mxResources.get('verticalTree'),
          null,
          mxUtils.bind(this, function () {
            var tmp = graph.getSelectionCell();
            var roots = null;

            if (tmp == null || graph.getModel().getChildCount(tmp) == 0) {
              if (graph.getModel().getEdgeCount(tmp) == 0) {
                roots = graph.findTreeRoots(graph.getDefaultParent());
              }
            } else {
              roots = graph.findTreeRoots(tmp);
            }

            if (roots != null && roots.length > 0) {
              tmp = roots[0];
            }

            if (tmp != null) {
              var layout = new mxCompactTreeLayout(graph, false);
              layout.edgeRouting = false;
              layout.levelDistance = 30;

              promptSpacing(
                layout.levelDistance,
                mxUtils.bind(this, function (newValue) {
                  layout.levelDistance = newValue;

                  this.editorUi.executeLayout(function () {
                    layout.execute(graph.getDefaultParent(), tmp);
                  }, true);
                })
              );
            }
          }),
          parent
        );
        menu.addItem(
          mxResources.get('radialTree'),
          null,
          mxUtils.bind(this, function () {
            var tmp = graph.getSelectionCell();
            var roots = null;

            if (tmp == null || graph.getModel().getChildCount(tmp) == 0) {
              if (graph.getModel().getEdgeCount(tmp) == 0) {
                roots = graph.findTreeRoots(graph.getDefaultParent());
              }
            } else {
              roots = graph.findTreeRoots(tmp);
            }

            if (roots != null && roots.length > 0) {
              tmp = roots[0];
            }

            if (tmp != null) {
              var layout = new mxRadialTreeLayout(graph, false);
              layout.levelDistance = 80;
              layout.autoRadius = true;

              promptSpacing(
                layout.levelDistance,
                mxUtils.bind(this, function (newValue) {
                  layout.levelDistance = newValue;

                  this.editorUi.executeLayout(function () {
                    layout.execute(graph.getDefaultParent(), tmp);

                    if (!graph.isSelectionEmpty()) {
                      tmp = graph.getModel().getParent(tmp);

                      if (graph.getModel().isVertex(tmp)) {
                        graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
                      }
                    }
                  }, true);
                })
              );
            }
          }),
          parent
        );
        menu.addSeparator(parent);
        menu.addItem(
          mxResources.get('organic'),
          null,
          mxUtils.bind(this, function () {
            var layout = new mxFastOrganicLayout(graph);

            promptSpacing(
              layout.forceConstant,
              mxUtils.bind(this, function (newValue) {
                layout.forceConstant = newValue;

                this.editorUi.executeLayout(function () {
                  var tmp = graph.getSelectionCell();

                  if (tmp == null || graph.getModel().getChildCount(tmp) == 0) {
                    tmp = graph.getDefaultParent();
                  }

                  layout.execute(tmp);

                  if (graph.getModel().isVertex(tmp)) {
                    graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
                  }
                }, true);
              })
            );
          }),
          parent
        );
        menu.addItem(
          mxResources.get('circle'),
          null,
          mxUtils.bind(this, function () {
            var layout = new mxCircleLayout(graph);

            this.editorUi.executeLayout(function () {
              var tmp = graph.getSelectionCell();

              if (tmp == null || graph.getModel().getChildCount(tmp) == 0) {
                tmp = graph.getDefaultParent();
              }

              layout.execute(tmp);

              if (graph.getModel().isVertex(tmp)) {
                graph.updateGroupBounds([tmp], graph.gridSize * 2, true);
              }
            }, true);
          }),
          parent
        );
      })
    )
  );
  this.put(
    'navigation',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        this.addMenuItems(
          menu,
          ['home', '-', 'exitGroup', 'enterGroup', '-', 'expand', 'collapse', '-', 'collapsible'],
          parent
        );
      })
    )
  );
  this.put(
    'arrange',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        this.addMenuItems(menu, ['toFront', 'toBack', '-'], parent);
        this.addSubmenu('direction', menu, parent);
        this.addMenuItems(menu, ['turn', '-'], parent);
        this.addSubmenu('align', menu, parent);
        this.addSubmenu('distribute', menu, parent);
        menu.addSeparator(parent);
        this.addSubmenu('navigation', menu, parent);
        this.addSubmenu('insert', menu, parent);
        this.addSubmenu('layout', menu, parent);
        this.addMenuItems(
          menu,
          ['-', 'group', 'ungroup', 'removeFromGroup', '-', 'clearWaypoints', 'autosize'],
          parent
        );
      })
    )
  ).isEnabled = isGraphEnabled;
  this.put(
    'insert',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        this.addMenuItems(menu, ['insertLink', 'insertImage'], parent);
      })
    )
  );
  this.put(
    'view',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        this.addMenuItems(
          menu,
          (this.editorUi.format != null ? ['formatPanel'] : []).concat(
            [
              'outline',
              'layers',
              '-',
              'pageView',
              'pageScale',
              '-',
              'scrollbars',
              'tooltips',
              '-',
              'grid',
              'guides',
              '-',
              'connectionArrows',
              'connectionPoints',
              '-',
              'resetView',
              'zoomIn',
              'zoomOut',
            ],
            parent
          )
        );
      })
    )
  );
  // Two special dropdowns that are only used in the toolbar
  this.put(
    'viewPanels',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        if (this.editorUi.format != null) {
          this.addMenuItems(menu, ['formatPanel'], parent);
        }

        this.addMenuItems(menu, ['outline', 'layers'], parent);
      })
    )
  );
  this.put(
    'viewZoom',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        this.addMenuItems(menu, ['resetView', '-'], parent);
        var scales = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3, 4];

        for (var i = 0; i < scales.length; i++) {
          (function (scale) {
            menu.addItem(
              scale * 100 + '%',
              null,
              function () {
                graph.zoomTo(scale);
              },
              parent
            );
          })(scales[i]);
        }

        this.addMenuItems(
          menu,
          ['-', 'fitWindow', 'fitPageWidth', 'fitPage', 'fitTwoPages', '-', 'customZoom'],
          parent
        );
      })
    )
  );
  this.put(
    'file',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        this.addMenuItems(
          menu,
          ['new', 'open', '-', 'save', 'saveAs', '-', 'import', 'export', '-', 'pageSetup', 'print'],
          parent
        );
      })
    )
  );
  this.put(
    'edit',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        this.addMenuItems(menu, [
          'undo',
          'redo',
          '-',
          'cut',
          'copy',
          'paste',
          'delete',
          '-',
          'duplicate',
          '-',
          'editData',
          'editTooltip',
          '-',
          'editStyle',
          '-',
          'edit',
          '-',
          'editLink',
          'openLink',
          '-',
          'selectVertices',
          'selectEdges',
          'selectAll',
          'selectNone',
          '-',
          'lockUnlock',
        ]);
      })
    )
  );
  this.put(
    'extras',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        this.addMenuItems(menu, ['copyConnect', 'collapseExpand', '-', 'editDiagram']);
      })
    )
  );
  this.put(
    'help',
    new Menu(
      mxUtils.bind(this, function (menu, parent) {
        this.addMenuItems(menu, ['help', '-', 'about']);
      })
    )
  );
};

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.put = function (name, menu) {
  this.menus[name] = menu;

  return menu;
};

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.get = function (name) {
  return this.menus[name];
};

/**
 * Adds the given submenu.
 */
Menus.prototype.addSubmenu = function (name, menu, parent, label) {
  var entry = this.get(name);

  if (entry != null) {
    var enabled = entry.isEnabled();

    if (menu.showDisabled || enabled) {
      var submenu = menu.addItem(label || mxResources.get(name), null, null, parent, null, enabled);
      this.addMenu(name, menu, submenu);
    }
  }
};

/**
 * Adds the label menu items to the given menu and parent.
 */
Menus.prototype.addMenu = function (name, popupMenu, parent) {
  var menu = this.get(name);

  if (menu != null && (popupMenu.showDisabled || menu.isEnabled())) {
    this.get(name).execute(popupMenu, parent);
  }
};

/**
 * Adds a menu item to insert a table cell.
 */
Menus.prototype.addInsertTableCellItem = function (menu, parent) {
  var graph = this.editorUi.editor.graph;

  this.addInsertTableItem(
    menu,
    mxUtils.bind(this, function (evt, rows, cols) {
      var table =
        mxEvent.isControlDown(evt) || mxEvent.isMetaDown(evt)
          ? graph.createCrossFunctionalSwimlane(rows, cols)
          : graph.createTable(rows, cols, null, null, mxEvent.isShiftDown(evt) ? 'Table' : null);
      var pt = mxEvent.isAltDown(evt)
        ? graph.getFreeInsertPoint()
        : graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry([table], true));
      var select = graph.importCells([table], pt.x, pt.y);

      if (select != null && select.length > 0) {
        graph.scrollCellToVisible(select[0]);
        graph.setSelectionCells(select);
      }
    }),
    parent
  );
};

/**
 * Adds a menu item to insert a table.
 */
Menus.prototype.addInsertTableItem = function (menu, insertFn, parent) {
  insertFn =
    insertFn != null
      ? insertFn
      : mxUtils.bind(this, function (evt, rows, cols) {
          var graph = this.editorUi.editor.graph;
          var td = graph.getParentByName(mxEvent.getSource(evt), 'TD');

          if (td != null && graph.cellEditor.textarea != null) {
            var row2 = graph.getParentByName(td, 'TR');

            // To find the new link, we create a list of all existing links first
            // LATER: Refactor for reuse with code for finding inserted image below
            var tmp = graph.cellEditor.textarea.getElementsByTagName('table');
            var oldTables = [];

            for (var i = 0; i < tmp.length; i++) {
              oldTables.push(tmp[i]);
            }

            // Finding the new table will work with insertHTML, but IE does not support that
            graph.container.focus();
            graph.pasteHtmlAtCaret(createTable(rows, cols));

            // Moves cursor to first table cell
            var newTables = graph.cellEditor.textarea.getElementsByTagName('table');

            if (newTables.length == oldTables.length + 1) {
              // Inverse order in favor of appended tables
              for (var i = newTables.length - 1; i >= 0; i--) {
                if (i == 0 || newTables[i] != oldTables[i - 1]) {
                  graph.selectNode(newTables[i].rows[0].cells[0]);
                  break;
                }
              }
            }
          }
        });

  // KNOWN: Does not work in IE8 standards and quirks
  var graph = this.editorUi.editor.graph;
  var row2 = null;
  var td = null;

  function createTable(rows, cols) {
    var html = ['<table>'];

    for (var i = 0; i < rows; i++) {
      html.push('<tr>');

      for (var j = 0; j < cols; j++) {
        html.push('<td><br></td>');
      }

      html.push('</tr>');
    }

    html.push('</table>');

    return html.join('');
  }

  // Show table size dialog
  var elt2 = menu.addItem('', null, null, parent, null, null, null, true);

  // Quirks mode does not add cell padding if cell is empty, needs good old spacer solution
  var quirksCellHtml = '<img src="' + mxClient.imageBasePath + '/transparent.gif' + '" width="16" height="16"/>';

  function createPicker(rows, cols) {
    var table2 = document.createElement('table');
    table2.setAttribute('border', '1');
    table2.style.borderCollapse = 'collapse';
    table2.style.borderStyle = 'solid';

    if (!mxClient.IS_QUIRKS) {
      table2.setAttribute('cellPadding', '8');
    }

    for (var i = 0; i < rows; i++) {
      var row = table2.insertRow(i);

      for (var j = 0; j < cols; j++) {
        var cell = row.insertCell(-1);

        if (mxClient.IS_QUIRKS) {
          cell.innerHTML = quirksCellHtml;
        }
      }
    }

    return table2;
  }

  function extendPicker(picker, rows, cols) {
    for (var i = picker.rows.length; i < rows; i++) {
      var row = picker.insertRow(i);

      for (var j = 0; j < picker.rows[0].cells.length; j++) {
        var cell = row.insertCell(-1);

        if (mxClient.IS_QUIRKS) {
          cell.innerHTML = quirksCellHtml;
        }
      }
    }

    for (var i = 0; i < picker.rows.length; i++) {
      var row = picker.rows[i];

      for (var j = row.cells.length; j < cols; j++) {
        var cell = row.insertCell(-1);

        if (mxClient.IS_QUIRKS) {
          cell.innerHTML = quirksCellHtml;
        }
      }
    }
  }

  elt2.firstChild.innerHTML = '';
  var picker = createPicker(5, 5);
  elt2.firstChild.appendChild(picker);

  var label = document.createElement('div');
  label.style.padding = '4px';
  label.style.fontSize = Menus.prototype.defaultFontSize + 'px';
  label.innerHTML = '1x1';
  elt2.firstChild.appendChild(label);

  function mouseover(e) {
    td = graph.getParentByName(mxEvent.getSource(e), 'TD');
    var selected = false;

    if (td != null) {
      row2 = graph.getParentByName(td, 'TR');
      var ext = mxEvent.isMouseEvent(e) ? 2 : 4;
      extendPicker(picker, Math.min(20, row2.sectionRowIndex + ext), Math.min(20, td.cellIndex + ext));
      label.innerHTML = td.cellIndex + 1 + 'x' + (row2.sectionRowIndex + 1);

      for (var i = 0; i < picker.rows.length; i++) {
        var r = picker.rows[i];

        for (var j = 0; j < r.cells.length; j++) {
          var cell = r.cells[j];

          if (i == row2.sectionRowIndex && j == td.cellIndex) {
            selected = cell.style.backgroundColor == 'blue';
          }

          if (i <= row2.sectionRowIndex && j <= td.cellIndex) {
            cell.style.backgroundColor = 'blue';
          } else {
            cell.style.backgroundColor = 'transparent';
          }
        }
      }
    }

    mxEvent.consume(e);

    return selected;
  }

  mxEvent.addGestureListeners(
    picker,
    null,
    null,
    mxUtils.bind(this, function (e) {
      var selected = mouseover(e);

      if (td != null && row2 != null && selected) {
        insertFn(e, row2.sectionRowIndex + 1, td.cellIndex + 1);

        // Async required to block event for elements under menu
        window.setTimeout(
          mxUtils.bind(this, function () {
            this.editorUi.hideCurrentMenu();
          }),
          0
        );
      }
    })
  );
  mxEvent.addListener(picker, 'mouseover', mouseover);
};

/**
 * Adds a style change item to the given menu.
 */
Menus.prototype.edgeStyleChange = function (menu, label, keys, values, sprite, parent, reset) {
  return menu.addItem(
    label,
    null,
    mxUtils.bind(this, function () {
      var graph = this.editorUi.editor.graph;
      graph.stopEditing(false);

      graph.getModel().beginUpdate();
      try {
        var cells = graph.getSelectionCells();
        var edges = [];

        for (var i = 0; i < cells.length; i++) {
          var cell = cells[i];

          if (graph.getModel().isEdge(cell)) {
            if (reset) {
              var geo = graph.getCellGeometry(cell);

              // Resets all edge points
              if (geo != null) {
                geo = geo.clone();
                geo.points = null;
                graph.getModel().setGeometry(cell, geo);
              }
            }

            for (var j = 0; j < keys.length; j++) {
              graph.setCellStyles(keys[j], values[j], [cell]);
            }

            edges.push(cell);
          }
        }

        this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', keys, 'values', values, 'cells', edges));
      } finally {
        graph.getModel().endUpdate();
      }
    }),
    parent,
    sprite
  );
};

/**
 * Adds a style change item to the given menu.
 */
Menus.prototype.styleChange = function (menu, label, keys, values, sprite, parent, fn, post) {
  var apply = this.createStyleChangeFunction(keys, values);

  return menu.addItem(
    label,
    null,
    mxUtils.bind(this, function () {
      var graph = this.editorUi.editor.graph;

      if (fn != null && graph.cellEditor.isContentEditing()) {
        fn();
      } else {
        apply(post);
      }
    }),
    parent,
    sprite
  );
};

/**
 *
 */
Menus.prototype.createStyleChangeFunction = function (keys, values) {
  return mxUtils.bind(this, function (post) {
    var graph = this.editorUi.editor.graph;
    graph.stopEditing(false);

    graph.getModel().beginUpdate();
    try {
      var cells = graph.getSelectionCells();

      for (var i = 0; i < keys.length; i++) {
        graph.setCellStyles(keys[i], values[i], cells);

        // Removes CSS alignment to produce consistent output
        if (keys[i] == mxConstants.STYLE_ALIGN) {
          graph.updateLabelElements(cells, function (elt) {
            elt.removeAttribute('align');
            elt.style.textAlign = null;
          });
        }

        // Updates autosize after font changes
        if (keys[i] == mxConstants.STYLE_FONTFAMILY) {
          for (var j = 0; j < cells.length; j++) {
            if (graph.model.getChildCount(cells[j]) == 0) {
              graph.autoSizeCell(cells[j], false);
            }
          }
        }
      }

      if (post != null) {
        post();
      }

      this.editorUi.fireEvent(new mxEventObject('styleChanged', 'keys', keys, 'values', values, 'cells', cells));
    } finally {
      graph.getModel().endUpdate();
    }
  });
};

/**
 * Adds a style change item with a prompt to the given menu.
 */
Menus.prototype.promptChange = function (menu, label, hint, defaultValue, key, parent, enabled, fn, sprite) {
  return menu.addItem(
    label,
    null,
    mxUtils.bind(this, function () {
      var graph = this.editorUi.editor.graph;
      var value = defaultValue;
      var state = graph.getView().getState(graph.getSelectionCell());

      if (state != null) {
        value = state.style[key] || value;
      }

      var dlg = new FilenameDialog(
        this.editorUi,
        value,
        mxResources.get('apply'),
        mxUtils.bind(this, function (newValue) {
          if (newValue != null && newValue.length > 0) {
            graph.getModel().beginUpdate();
            try {
              graph.stopEditing(false);
              graph.setCellStyles(key, newValue);
            } finally {
              graph.getModel().endUpdate();
            }

            if (fn != null) {
              fn(newValue);
            }
          }
        }),
        mxResources.get('enterValue') + (hint.length > 0 ? ' ' + hint : '')
      );
      this.editorUi.showDialog(dlg.container, 300, 80, true, true);
      dlg.init();
    }),
    parent,
    sprite,
    enabled
  );
};

/**
 * Adds a handler for showing a menu in the given element.
 */
Menus.prototype.pickColor = function (key, cmd, defaultValue) {
  var graph = this.editorUi.editor.graph;
  var h =
    226 +
    (Math.ceil(ColorDialog.prototype.presetColors.length / 12) +
      Math.ceil(ColorDialog.prototype.defaultColors.length / 12)) *
      17;

  if (cmd != null && graph.cellEditor.isContentEditing()) {
    // Saves and restores text selection for in-place editor
    var selState = graph.cellEditor.saveSelection();

    var dlg = new ColorDialog(
      this.editorUi,
      defaultValue || '000000',
      mxUtils.bind(this, function (color) {
        graph.cellEditor.restoreSelection(selState);
        document.execCommand(cmd, false, color != mxConstants.NONE ? color : 'transparent');
      }),
      function () {
        graph.cellEditor.restoreSelection(selState);
      }
    );
    this.editorUi.showDialog(dlg.container, 230, h, true, true);
    dlg.init();
  } else {
    if (this.colorDialog == null) {
      this.colorDialog = new ColorDialog(this.editorUi);
    }

    this.colorDialog.currentColorKey = key;
    var state = graph.getView().getState(graph.getSelectionCell());
    var color = 'none';

    if (state != null) {
      color = state.style[key] || color;
    }

    if (color == 'none') {
      color = 'ffffff';
      this.colorDialog.picker.fromString('ffffff');
      this.colorDialog.colorInput.value = 'none';
    } else {
      this.colorDialog.picker.fromString(color);
    }

    this.editorUi.showDialog(this.colorDialog.container, 230, h, true, true);
    this.colorDialog.init();
  }
};

/**
 * Adds a handler for showing a menu in the given element.
 */
Menus.prototype.toggleStyle = function (key, defaultValue) {
  var graph = this.editorUi.editor.graph;
  var value = graph.toggleCellStyles(key, defaultValue);
  this.editorUi.fireEvent(
    new mxEventObject('styleChanged', 'keys', [key], 'values', [value], 'cells', graph.getSelectionCells())
  );
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addMenuItem = function (menu, key, parent, trigger, sprite, label) {
  var action = this.editorUi.actions.get(key);

  if (action != null && (menu.showDisabled || action.isEnabled()) && action.visible) {
    var item = menu.addItem(
      label || action.label,
      null,
      function () {
        action.funct(trigger);
      },
      parent,
      sprite,
      action.isEnabled()
    );

    // Adds checkmark image
    if (action.toggleAction && action.isSelected()) {
      menu.addCheckmark(item, Editor.checkmarkImage);
    }

    this.addShortcut(item, action);

    return item;
  }

  return null;
};

/**
 * Adds a checkmark to the given menuitem.
 */
Menus.prototype.addShortcut = function (item, action) {
  if (action.shortcut != null) {
    var td = item.firstChild.nextSibling.nextSibling;
    var span = document.createElement('span');
    span.style.color = 'gray';
    mxUtils.write(span, action.shortcut);
    td.appendChild(span);
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addMenuItems = function (menu, keys, parent, trigger, sprites) {
  for (var i = 0; i < keys.length; i++) {
    if (keys[i] == '-') {
      menu.addSeparator(parent);
    } else {
      this.addMenuItem(menu, keys[i], parent, trigger, sprites != null ? sprites[i] : null);
    }
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.createPopupMenu = function (menu, cell, evt) {
  menu.smartSeparators = true;

  this.addPopupMenuHistoryItems(menu, cell, evt);
  this.addPopupMenuEditItems(menu, cell, evt);
  this.addPopupMenuStyleItems(menu, cell, evt);
  this.addPopupMenuArrangeItems(menu, cell, evt);
  this.addPopupMenuCellItems(menu, cell, evt);
  this.addPopupMenuSelectionItems(menu, cell, evt);
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuHistoryItems = function (menu, cell, evt) {
  if (this.editorUi.editor.graph.isSelectionEmpty()) {
    this.addMenuItems(menu, ['undo', 'redo'], null, evt);
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuEditItems = function (menu, cell, evt) {
  if (this.editorUi.editor.graph.isSelectionEmpty()) {
    this.addMenuItems(menu, ['pasteHere'], null, evt);
  } else {
    this.addMenuItems(menu, ['delete', '-', 'cut', 'copy', '-', 'duplicate'], null, evt);
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuStyleItems = function (menu, cell, evt) {
  if (this.editorUi.editor.graph.getSelectionCount() == 1) {
    this.addMenuItems(menu, ['-', 'setAsDefaultStyle'], null, evt);
  } else if (this.editorUi.editor.graph.isSelectionEmpty()) {
    this.addMenuItems(menu, ['-', 'clearDefaultStyle'], null, evt);
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuArrangeItems = function (menu, cell, evt) {
  var graph = this.editorUi.editor.graph;

  if (!graph.isSelectionEmpty()) {
    this.addMenuItems(menu, ['-', 'toFront', 'toBack'], null, evt);
  }

  if (graph.getSelectionCount() > 1) {
    this.addMenuItems(menu, ['-', 'group'], null, evt);
  } else if (
    graph.getSelectionCount() == 1 &&
    !graph.getModel().isEdge(cell) &&
    !graph.isSwimlane(cell) &&
    graph.getModel().getChildCount(cell) > 0
  ) {
    this.addMenuItems(menu, ['-', 'ungroup'], null, evt);
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuCellItems = function (menu, cell, evt) {
  var graph = this.editorUi.editor.graph;
  cell = graph.getSelectionCell();
  var state = graph.view.getState(cell);
  menu.addSeparator();

  if (state != null) {
    var hasWaypoints = false;

    if (
      graph.getModel().isEdge(cell) &&
      mxUtils.getValue(state.style, mxConstants.STYLE_EDGE, null) != 'entityRelationEdgeStyle' &&
      mxUtils.getValue(state.style, mxConstants.STYLE_SHAPE, null) != 'arrow'
    ) {
      var handler = graph.selectionCellsHandler.getHandler(cell);
      var isWaypoint = false;

      if (handler instanceof mxEdgeHandler && handler.bends != null && handler.bends.length > 2) {
        var index = handler.getHandleForEvent(graph.updateMouseEvent(new mxMouseEvent(evt)));

        // Configures removeWaypoint action before execution
        // Using trigger parameter is cleaner but have to find waypoint here anyway.
        var rmWaypointAction = this.editorUi.actions.get('removeWaypoint');
        rmWaypointAction.handler = handler;
        rmWaypointAction.index = index;

        isWaypoint = index > 0 && index < handler.bends.length - 1;
      }

      menu.addSeparator();
      this.addMenuItem(menu, 'turn', null, evt, null, mxResources.get('reverse'));
      this.addMenuItems(menu, [isWaypoint ? 'removeWaypoint' : 'addWaypoint'], null, evt);

      // Adds reset waypoints option if waypoints exist
      var geo = graph.getModel().getGeometry(cell);
      hasWaypoints = geo != null && geo.points != null && geo.points.length > 0;
    }

    if (
      graph.getSelectionCount() == 1 &&
      (hasWaypoints || (graph.getModel().isVertex(cell) && graph.getModel().getEdgeCount(cell) > 0))
    ) {
      this.addMenuItems(menu, ['-', 'clearWaypoints'], null, evt);
    }

    if (graph.getSelectionCount() == 1) {
      this.addMenuItems(menu, ['-', 'editStyle', 'editData', 'editLink'], null, evt);

      // Shows edit image action if there is an image in the style
      if (graph.getModel().isVertex(cell) && mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE, null) != null) {
        menu.addSeparator();
        this.addMenuItem(menu, 'image', null, evt).firstChild.nextSibling.innerHTML =
          mxResources.get('editImage') + '...';
      }
    }
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.addPopupMenuSelectionItems = function (menu, cell, evt) {
  if (this.editorUi.editor.graph.isSelectionEmpty()) {
    this.addMenuItems(menu, ['-', 'selectVertices', 'selectEdges', 'selectAll'], null, evt);
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.createMenubar = function (container) {
  var menubar = new Menubar(this.editorUi, container);
  var menus = this.defaultMenuItems;

  for (var i = 0; i < menus.length; i++) {
    mxUtils.bind(this, function (menu) {
      var elt = menubar.addMenu(
        mxResources.get(menus[i]),
        mxUtils.bind(this, function () {
          // Allows extensions of menu.funct
          menu.funct.apply(this, arguments);
        })
      );

      this.menuCreated(menu, elt);
    })(this.get(menus[i]));
  }

  return menubar;
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menus.prototype.menuCreated = function (menu, elt, className) {
  if (elt != null) {
    className = className != null ? className : 'geItem';

    menu.addListener('stateChanged', function () {
      elt.enabled = menu.enabled;

      if (!menu.enabled) {
        elt.className = className + ' mxDisabled';

        if (document.documentMode == 8) {
          elt.style.color = '#c3c3c3';
        }
      } else {
        elt.className = className;

        if (document.documentMode == 8) {
          elt.style.color = '';
        }
      }
    });
  }
};

/**
 * Construcs a new menubar for the given editor.
 */
function Menubar(editorUi, container) {
  this.editorUi = editorUi;
  this.container = container;
}

/**
 * Adds the menubar elements.
 */
Menubar.prototype.hideMenu = function () {
  this.editorUi.hideCurrentMenu();
};

/**
 * Adds a submenu to this menubar.
 */
Menubar.prototype.addMenu = function (label, funct, before) {
  var elt = document.createElement('a');
  elt.className = 'geItem';
  mxUtils.write(elt, label);
  this.addMenuHandler(elt, funct);

  if (before != null) {
    this.container.insertBefore(elt, before);
  } else {
    this.container.appendChild(elt);
  }

  return elt;
};

/**
 * Adds a handler for showing a menu in the given element.
 */
Menubar.prototype.addMenuHandler = function (elt, funct) {
  if (funct != null) {
    var show = true;

    var clickHandler = mxUtils.bind(this, function (evt) {
      if ((show && elt.enabled == null) || elt.enabled) {
        this.editorUi.editor.graph.popupMenuHandler.hideMenu();
        var menu = new mxPopupMenu(funct, this.editorUi.container);
        menu.div.className += ' geMenubarMenu';
        menu.smartSeparators = true;
        menu.showDisabled = true;
        menu.autoExpand = true;

        // Disables autoexpand and destroys menu when hidden
        menu.hideMenu = mxUtils.bind(this, function () {
          mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
          this.editorUi.resetCurrentMenu();
          menu.destroy();
        });

        var offset = mxUtils.getOffset(elt, null, true);
        menu.popup(offset.x, offset.y + elt.offsetHeight, null, evt);
        this.editorUi.setCurrentMenu(menu, elt);
      }

      mxEvent.consume(evt);
    });

    // Shows menu automatically while in expanded state
    mxEvent.addListener(
      elt,
      'mousemove',
      mxUtils.bind(this, function (evt) {
        if (this.editorUi.currentMenu != null && this.editorUi.currentMenuElt != elt) {
          this.editorUi.hideCurrentMenu();
          clickHandler(evt);
        }
      })
    );

    // Hides menu if already showing and prevents focus
    mxEvent.addListener(
      elt,
      mxClient.IS_POINTER ? 'pointerdown' : 'mousedown',
      mxUtils.bind(this, function (evt) {
        show = this.currentElt != elt;
        evt.preventDefault();
      })
    );

    mxEvent.addListener(
      elt,
      'click',
      mxUtils.bind(this, function (evt) {
        clickHandler(evt);
        show = true;
      })
    );
  }
};

/**
 * Creates the keyboard event handler for the current graph and history.
 */
Menubar.prototype.destroy = function () {
  // do nothing
};

/**
 * Constructs a new action for the given parameters.
 */
function Menu(funct, enabled) {
  mxEventSource.call(this);
  this.funct = funct;
  this.enabled = enabled != null ? enabled : true;
}

// Menu inherits from mxEventSource
mxUtils.extend(Menu, mxEventSource);

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Menu.prototype.isEnabled = function () {
  return this.enabled;
};

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Menu.prototype.setEnabled = function (value) {
  if (this.enabled != value) {
    this.enabled = value;
    this.fireEvent(new mxEventObject('stateChanged'));
  }
};

/**
 * Sets the enabled state of the action and fires a stateChanged event.
 */
Menu.prototype.execute = function (menu, parent) {
  this.funct(menu, parent);
};

/**
 * "Installs" menus in EditorUi.
 */
EditorUi.prototype.createMenus = function () {
  return new Menus(this);
};
/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
/**
 * Construcs a new toolbar for the given editor.
 */
function Toolbar(editorUi, container) {
  this.editorUi = editorUi;
  this.container = container;
  this.staticElements = [];
  this.init();

  // Global handler to hide the current menu
  this.gestureHandler = mxUtils.bind(this, function (evt) {
    if (this.editorUi.currentMenu != null && mxEvent.getSource(evt) != this.editorUi.currentMenu.div) {
      this.hideMenu();
    }
  });

  mxEvent.addGestureListeners(document, this.gestureHandler);
}

/**
 * Image for the dropdown arrow.
 */
Toolbar.prototype.dropdownImage = !mxClient.IS_SVG
  ? IMAGE_PATH + '/dropdown.gif'
  : 'data:image/gif;base64,R0lGODlhDQANAIABAHt7e////yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpCREM1NkJFMjE0NEMxMUU1ODk1Q0M5MjQ0MTA4QjNDMSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpCREM1NkJFMzE0NEMxMUU1ODk1Q0M5MjQ0MTA4QjNDMSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkQzOUMzMjZCMTQ0QjExRTU4OTVDQzkyNDQxMDhCM0MxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkQzOUMzMjZDMTQ0QjExRTU4OTVDQzkyNDQxMDhCM0MxIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Af/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCgkIBwYFBAMCAQAAIfkEAQAAAQAsAAAAAA0ADQAAAhGMj6nL3QAjVHIu6azbvPtWAAA7';

/**
 * Image element for the dropdown arrow.
 */
Toolbar.prototype.dropdownImageHtml =
  '<img border="0" style="position:absolute;right:4px;top:' +
  (!EditorUi.compactUi ? 8 : 6) +
  'px;" src="' +
  Toolbar.prototype.dropdownImage +
  '" valign="middle"/>';

/**
 * Defines the background for selected buttons.
 */
Toolbar.prototype.selectedBackground = '#d0d0d0';

/**
 * Defines the background for selected buttons.
 */
Toolbar.prototype.unselectedBackground = 'none';

/**
 * Array that contains the DOM nodes that should never be removed.
 */
Toolbar.prototype.staticElements = null;

/**
 * Adds the toolbar elements.
 */
Toolbar.prototype.init = function () {
  var sw = screen.width;

  // Takes into account initial compact mode
  sw -= screen.height > 740 ? 56 : 0;

  if (sw >= 700) {
    var formatMenu = this.addMenu(
      '',
      mxResources.get('view') + ' (' + mxResources.get('panTooltip') + ')',
      true,
      'viewPanels',
      null,
      true
    );
    this.addDropDownArrow(formatMenu, 'geSprite-formatpanel', 38, 50, -4, -3, 36, -8);
    this.addSeparator();
  }

  var viewMenu = this.addMenu('', mxResources.get('zoom') + ' (Alt+Mousewheel)', true, 'viewZoom', null, true);
  viewMenu.showDisabled = true;
  viewMenu.style.whiteSpace = 'nowrap';
  viewMenu.style.position = 'relative';
  viewMenu.style.overflow = 'hidden';

  if (EditorUi.compactUi) {
    viewMenu.style.width = mxClient.IS_QUIRKS ? '58px' : '50px';
  } else {
    viewMenu.style.width = mxClient.IS_QUIRKS ? '62px' : '36px';
  }

  if (sw >= 420) {
    this.addSeparator();
    var elts = this.addItems(['zoomIn', 'zoomOut']);
    elts[0].setAttribute(
      'title',
      mxResources.get('zoomIn') + ' (' + this.editorUi.actions.get('zoomIn').shortcut + ')'
    );
    elts[1].setAttribute(
      'title',
      mxResources.get('zoomOut') + ' (' + this.editorUi.actions.get('zoomOut').shortcut + ')'
    );
  }

  // Updates the label if the scale changes
  this.updateZoom = mxUtils.bind(this, function () {
    viewMenu.innerHTML = Math.round(this.editorUi.editor.graph.view.scale * 100) + '%' + this.dropdownImageHtml;

    if (EditorUi.compactUi) {
      viewMenu.getElementsByTagName('img')[0].style.right = '1px';
      viewMenu.getElementsByTagName('img')[0].style.top = '5px';
    }
  });

  this.editorUi.editor.graph.view.addListener(mxEvent.EVENT_SCALE, this.updateZoom);
  this.editorUi.editor.addListener('resetGraphView', this.updateZoom);

  var elts = this.addItems(['-', 'undo', 'redo']);
  elts[1].setAttribute('title', mxResources.get('undo') + ' (' + this.editorUi.actions.get('undo').shortcut + ')');
  elts[2].setAttribute('title', mxResources.get('redo') + ' (' + this.editorUi.actions.get('redo').shortcut + ')');

  if (sw >= 320) {
    var elts = this.addItems(['-', 'delete']);
    elts[1].setAttribute(
      'title',
      mxResources.get('delete') + ' (' + this.editorUi.actions.get('delete').shortcut + ')'
    );
  }

  if (sw >= 550) {
    this.addItems(['-', 'toFront', 'toBack']);
  }

  if (sw >= 740) {
    this.addItems(['-', 'fillColor']);

    if (sw >= 780) {
      this.addItems(['strokeColor']);

      if (sw >= 820) {
        this.addItems(['shadow']);
      }
    }
  }

  if (sw >= 400) {
    this.addSeparator();

    if (sw >= 440) {
      this.edgeShapeMenu = this.addMenuFunction(
        '',
        mxResources.get('connection'),
        false,
        mxUtils.bind(this, function (menu) {
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_SHAPE, 'width'],
              [null, null],
              'geIcon geSprite geSprite-connection',
              null,
              true
            )
            .setAttribute('title', mxResources.get('line'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_SHAPE, 'width'],
              ['link', null],
              'geIcon geSprite geSprite-linkedge',
              null,
              true
            )
            .setAttribute('title', mxResources.get('link'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_SHAPE, 'width'],
              ['flexArrow', null],
              'geIcon geSprite geSprite-arrow',
              null,
              true
            )
            .setAttribute('title', mxResources.get('arrow'));
          this.editorUi.menus
            .edgeStyleChange(
              menu,
              '',
              [mxConstants.STYLE_SHAPE, 'width'],
              ['arrow', null],
              'geIcon geSprite geSprite-simplearrow',
              null,
              true
            )
            .setAttribute('title', mxResources.get('simpleArrow'));
        })
      );

      this.addDropDownArrow(this.edgeShapeMenu, 'geSprite-connection', 44, 50, 0, 0, 22, -4);
    }

    this.edgeStyleMenu = this.addMenuFunction(
      'geSprite-orthogonal',
      mxResources.get('waypoints'),
      false,
      mxUtils.bind(this, function (menu) {
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            [null, null, null],
            'geIcon geSprite geSprite-straight',
            null,
            true
          )
          .setAttribute('title', mxResources.get('straight'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['orthogonalEdgeStyle', null, null],
            'geIcon geSprite geSprite-orthogonal',
            null,
            true
          )
          .setAttribute('title', mxResources.get('orthogonal'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['elbowEdgeStyle', null, null, null],
            'geIcon geSprite geSprite-horizontalelbow',
            null,
            true
          )
          .setAttribute('title', mxResources.get('simple'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['elbowEdgeStyle', 'vertical', null, null],
            'geIcon geSprite geSprite-verticalelbow',
            null,
            true
          )
          .setAttribute('title', mxResources.get('simple'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['isometricEdgeStyle', null, null, null],
            'geIcon geSprite geSprite-horizontalisometric',
            null,
            true
          )
          .setAttribute('title', mxResources.get('isometric'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_ELBOW, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['isometricEdgeStyle', 'vertical', null, null],
            'geIcon geSprite geSprite-verticalisometric',
            null,
            true
          )
          .setAttribute('title', mxResources.get('isometric'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['orthogonalEdgeStyle', '1', null],
            'geIcon geSprite geSprite-curved',
            null,
            true
          )
          .setAttribute('title', mxResources.get('curved'));
        this.editorUi.menus
          .edgeStyleChange(
            menu,
            '',
            [mxConstants.STYLE_EDGE, mxConstants.STYLE_CURVED, mxConstants.STYLE_NOEDGESTYLE],
            ['entityRelationEdgeStyle', null, null],
            'geIcon geSprite geSprite-entity',
            null,
            true
          )
          .setAttribute('title', mxResources.get('entityRelation'));
      })
    );

    this.addDropDownArrow(this.edgeStyleMenu, 'geSprite-orthogonal', 44, 50, 0, 0, 22, -4);
  }

  this.addSeparator();
  var insertMenu = this.addMenu(
    '',
    mxResources.get('insert') + ' (' + mxResources.get('doubleClickTooltip') + ')',
    true,
    'insert',
    null,
    true
  );
  this.addDropDownArrow(insertMenu, 'geSprite-plus', 38, 48, -4, -3, 36, -8);
  this.addTableDropDown();
};

/**
 * Adds the toolbar elements.
 */
Toolbar.prototype.addTableDropDown = function () {
  this.addSeparator();

  // KNOWN: All table stuff does not work with undo/redo
  // KNOWN: Lost focus after click on submenu with text (not icon) in quirks and IE8. This is because the TD seems
  // to catch the focus on click in these browsers. NOTE: Workaround in mxPopupMenu for icon items (without text).
  var menuElt = this.addMenuFunction(
    'geIcon geSprite geSprite-table',
    mxResources.get('table'),
    false,
    mxUtils.bind(this, function (menu) {
      var graph = this.editorUi.editor.graph;
      var cell = graph.getSelectionCell();

      if (!graph.isTableCell(cell) && !graph.isTableRow(cell) && !graph.isTable(cell)) {
        this.editorUi.menus.addInsertTableCellItem(menu);
      } else {
        var elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            try {
              graph.insertTableColumn(cell, true);
            } catch (e) {
              this.editorUi.handleError(e);
            }
          }),
          null,
          'geIcon geSprite geSprite-insertcolumnbefore'
        );
        elt.setAttribute('title', mxResources.get('insertColumnBefore'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            try {
              graph.insertTableColumn(cell, false);
            } catch (e) {
              this.editorUi.handleError(e);
            }
          }),
          null,
          'geIcon geSprite geSprite-insertcolumnafter'
        );
        elt.setAttribute('title', mxResources.get('insertColumnAfter'));

        elt = menu.addItem(
          'Delete column',
          null,
          mxUtils.bind(this, function () {
            if (cell != null) {
              try {
                graph.deleteTableColumn(cell);
              } catch (e) {
                this.editorUi.handleError(e);
              }
            }
          }),
          null,
          'geIcon geSprite geSprite-deletecolumn'
        );
        elt.setAttribute('title', mxResources.get('deleteColumn'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            try {
              graph.insertTableRow(cell, true);
            } catch (e) {
              this.editorUi.handleError(e);
            }
          }),
          null,
          'geIcon geSprite geSprite-insertrowbefore'
        );
        elt.setAttribute('title', mxResources.get('insertRowBefore'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            try {
              graph.insertTableRow(cell, false);
            } catch (e) {
              this.editorUi.handleError(e);
            }
          }),
          null,
          'geIcon geSprite geSprite-insertrowafter'
        );
        elt.setAttribute('title', mxResources.get('insertRowAfter'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            try {
              graph.deleteTableRow(cell);
            } catch (e) {
              this.editorUi.handleError(e);
            }
          }),
          null,
          'geIcon geSprite geSprite-deleterow'
        );
        elt.setAttribute('title', mxResources.get('deleteRow'));
      }
    })
  );

  menuElt.style.position = 'relative';
  menuElt.style.whiteSpace = 'nowrap';
  menuElt.style.overflow = 'hidden';
  menuElt.innerHTML = '<div class="geSprite geSprite-table" style="margin-left:-2px;"></div>' + this.dropdownImageHtml;
  menuElt.style.width = mxClient.IS_QUIRKS ? '50px' : '30px';

  // Fix for item size in kennedy theme
  if (EditorUi.compactUi) {
    menuElt.getElementsByTagName('img')[0].style.left = '22px';
    menuElt.getElementsByTagName('img')[0].style.top = '5px';
  }

  // Connects to insert menu enabled state
  var menu = this.editorUi.menus.get('insert');

  // Workaround for possible not a function
  // when extending HTML objects
  if (menu != null && typeof menuElt.setEnabled === 'function') {
    menu.addListener('stateChanged', function () {
      menuElt.setEnabled(menu.enabled);
    });
  }

  return menuElt;
};

/**
 * Adds the toolbar elements.
 */
Toolbar.prototype.addDropDownArrow = function (menu, sprite, width, atlasWidth, left, top, atlasDelta, atlasLeft) {
  atlasDelta = atlasDelta != null ? atlasDelta : 32;
  left = EditorUi.compactUi ? left : atlasLeft;

  menu.style.whiteSpace = 'nowrap';
  menu.style.overflow = 'hidden';
  menu.style.position = 'relative';
  menu.innerHTML =
    '<div class="geSprite ' +
    sprite +
    '" style="margin-left:' +
    left +
    'px;margin-top:' +
    top +
    'px;"></div>' +
    this.dropdownImageHtml;
  menu.style.width = mxClient.IS_QUIRKS ? atlasWidth + 'px' : atlasWidth - atlasDelta + 'px';

  if (mxClient.IS_QUIRKS) {
    menu.style.height = EditorUi.compactUi ? '24px' : '26px';
  }

  // Fix for item size in kennedy theme
  if (EditorUi.compactUi) {
    menu.getElementsByTagName('img')[0].style.left = '24px';
    menu.getElementsByTagName('img')[0].style.top = '5px';
    menu.style.width = mxClient.IS_QUIRKS ? width + 'px' : width - 10 + 'px';
  }
};

/**
 * Sets the current font name.
 */
Toolbar.prototype.setFontName = function (value) {
  if (this.fontMenu != null) {
    this.fontMenu.innerHTML =
      '<div style="width:60px;overflow:hidden;display:inline-block;">' +
      mxUtils.htmlEntities(value) +
      '</div>' +
      this.dropdownImageHtml;
  }
};

/**
 * Sets the current font name.
 */
Toolbar.prototype.setFontSize = function (value) {
  if (this.sizeMenu != null) {
    this.sizeMenu.innerHTML =
      '<div style="width:24px;overflow:hidden;display:inline-block;">' +
      mxUtils.htmlEntities(value) +
      '</div>' +
      this.dropdownImageHtml;
  }
};

/**
 * Hides the current menu.
 */
Toolbar.prototype.createTextToolbar = function () {
  var graph = this.editorUi.editor.graph;

  var styleElt = this.addMenu('', mxResources.get('style'), true, 'formatBlock');
  styleElt.style.position = 'relative';
  styleElt.style.whiteSpace = 'nowrap';
  styleElt.style.overflow = 'hidden';
  styleElt.innerHTML = mxResources.get('style') + this.dropdownImageHtml;

  if (EditorUi.compactUi) {
    styleElt.style.paddingRight = '18px';
    styleElt.getElementsByTagName('img')[0].style.right = '1px';
    styleElt.getElementsByTagName('img')[0].style.top = '5px';
  }

  this.addSeparator();

  this.fontMenu = this.addMenu('', mxResources.get('fontFamily'), true, 'fontFamily');
  this.fontMenu.style.position = 'relative';
  this.fontMenu.style.whiteSpace = 'nowrap';
  this.fontMenu.style.overflow = 'hidden';
  this.fontMenu.style.width = mxClient.IS_QUIRKS ? '80px' : '60px';

  this.setFontName(Menus.prototype.defaultFont);

  if (EditorUi.compactUi) {
    this.fontMenu.style.paddingRight = '18px';
    this.fontMenu.getElementsByTagName('img')[0].style.right = '1px';
    this.fontMenu.getElementsByTagName('img')[0].style.top = '5px';
  }

  this.addSeparator();

  this.sizeMenu = this.addMenu(Menus.prototype.defaultFontSize, mxResources.get('fontSize'), true, 'fontSize');
  this.sizeMenu.style.position = 'relative';
  this.sizeMenu.style.whiteSpace = 'nowrap';
  this.sizeMenu.style.overflow = 'hidden';
  this.sizeMenu.style.width = mxClient.IS_QUIRKS ? '44px' : '24px';

  this.setFontSize(Menus.prototype.defaultFontSize);

  if (EditorUi.compactUi) {
    this.sizeMenu.style.paddingRight = '18px';
    this.sizeMenu.getElementsByTagName('img')[0].style.right = '1px';
    this.sizeMenu.getElementsByTagName('img')[0].style.top = '5px';
  }

  var elts = this.addItems(['-', 'undo', 'redo', '-', 'bold', 'italic', 'underline']);
  elts[1].setAttribute('title', mxResources.get('undo') + ' (' + this.editorUi.actions.get('undo').shortcut + ')');
  elts[2].setAttribute('title', mxResources.get('redo') + ' (' + this.editorUi.actions.get('redo').shortcut + ')');
  elts[4].setAttribute('title', mxResources.get('bold') + ' (' + this.editorUi.actions.get('bold').shortcut + ')');
  elts[5].setAttribute('title', mxResources.get('italic') + ' (' + this.editorUi.actions.get('italic').shortcut + ')');
  elts[6].setAttribute(
    'title',
    mxResources.get('underline') + ' (' + this.editorUi.actions.get('underline').shortcut + ')'
  );

  // KNOWN: Lost focus after click on submenu with text (not icon) in quirks and IE8. This is because the TD seems
  // to catch the focus on click in these browsers. NOTE: Workaround in mxPopupMenu for icon items (without text).
  var alignMenu = this.addMenuFunction(
    '',
    mxResources.get('align'),
    false,
    mxUtils.bind(this, function (menu) {
      elt = menu.addItem(
        '',
        null,
        mxUtils.bind(this, function (evt) {
          graph.cellEditor.alignText(mxConstants.ALIGN_LEFT, evt);
        }),
        null,
        'geIcon geSprite geSprite-left'
      );
      elt.setAttribute('title', mxResources.get('left'));

      elt = menu.addItem(
        '',
        null,
        mxUtils.bind(this, function (evt) {
          graph.cellEditor.alignText(mxConstants.ALIGN_CENTER, evt);
        }),
        null,
        'geIcon geSprite geSprite-center'
      );
      elt.setAttribute('title', mxResources.get('center'));

      elt = menu.addItem(
        '',
        null,
        mxUtils.bind(this, function (evt) {
          graph.cellEditor.alignText(mxConstants.ALIGN_RIGHT, evt);
        }),
        null,
        'geIcon geSprite geSprite-right'
      );
      elt.setAttribute('title', mxResources.get('right'));

      elt = menu.addItem(
        '',
        null,
        mxUtils.bind(this, function () {
          document.execCommand('justifyfull', false, null);
        }),
        null,
        'geIcon geSprite geSprite-justifyfull'
      );
      elt.setAttribute('title', mxResources.get('justifyfull'));

      elt = menu.addItem(
        '',
        null,
        mxUtils.bind(this, function () {
          document.execCommand('insertorderedlist', false, null);
        }),
        null,
        'geIcon geSprite geSprite-orderedlist'
      );
      elt.setAttribute('title', mxResources.get('numberedList'));

      elt = menu.addItem(
        '',
        null,
        mxUtils.bind(this, function () {
          document.execCommand('insertunorderedlist', false, null);
        }),
        null,
        'geIcon geSprite geSprite-unorderedlist'
      );
      elt.setAttribute('title', mxResources.get('bulletedList'));

      elt = menu.addItem(
        '',
        null,
        mxUtils.bind(this, function () {
          document.execCommand('outdent', false, null);
        }),
        null,
        'geIcon geSprite geSprite-outdent'
      );
      elt.setAttribute('title', mxResources.get('decreaseIndent'));

      elt = menu.addItem(
        '',
        null,
        mxUtils.bind(this, function () {
          document.execCommand('indent', false, null);
        }),
        null,
        'geIcon geSprite geSprite-indent'
      );
      elt.setAttribute('title', mxResources.get('increaseIndent'));
    })
  );

  alignMenu.style.position = 'relative';
  alignMenu.style.whiteSpace = 'nowrap';
  alignMenu.style.overflow = 'hidden';
  alignMenu.innerHTML = '<div class="geSprite geSprite-left" style="margin-left:-2px;"></div>' + this.dropdownImageHtml;
  alignMenu.style.width = mxClient.IS_QUIRKS ? '50px' : '30px';

  if (EditorUi.compactUi) {
    alignMenu.getElementsByTagName('img')[0].style.left = '22px';
    alignMenu.getElementsByTagName('img')[0].style.top = '5px';
  }

  var formatMenu = this.addMenuFunction(
    '',
    mxResources.get('format'),
    false,
    mxUtils.bind(this, function (menu) {
      elt = menu.addItem(
        '',
        null,
        this.editorUi.actions.get('subscript').funct,
        null,
        'geIcon geSprite geSprite-subscript'
      );
      elt.setAttribute('title', mxResources.get('subscript') + ' (' + Editor.ctrlKey + '+,)');

      elt = menu.addItem(
        '',
        null,
        this.editorUi.actions.get('superscript').funct,
        null,
        'geIcon geSprite geSprite-superscript'
      );
      elt.setAttribute('title', mxResources.get('superscript') + ' (' + Editor.ctrlKey + '+.)');

      // KNOWN: IE+FF don't return keyboard focus after color dialog (calling focus doesn't help)
      elt = menu.addItem(
        '',
        null,
        this.editorUi.actions.get('fontColor').funct,
        null,
        'geIcon geSprite geSprite-fontcolor'
      );
      elt.setAttribute('title', mxResources.get('fontColor'));

      elt = menu.addItem(
        '',
        null,
        this.editorUi.actions.get('backgroundColor').funct,
        null,
        'geIcon geSprite geSprite-fontbackground'
      );
      elt.setAttribute('title', mxResources.get('backgroundColor'));

      elt = menu.addItem(
        '',
        null,
        mxUtils.bind(this, function () {
          document.execCommand('removeformat', false, null);
        }),
        null,
        'geIcon geSprite geSprite-removeformat'
      );
      elt.setAttribute('title', mxResources.get('removeFormat'));
    })
  );

  formatMenu.style.position = 'relative';
  formatMenu.style.whiteSpace = 'nowrap';
  formatMenu.style.overflow = 'hidden';
  formatMenu.innerHTML =
    '<div class="geSprite geSprite-dots" style="margin-left:-2px;"></div>' + this.dropdownImageHtml;
  formatMenu.style.width = mxClient.IS_QUIRKS ? '50px' : '30px';

  if (EditorUi.compactUi) {
    formatMenu.getElementsByTagName('img')[0].style.left = '22px';
    formatMenu.getElementsByTagName('img')[0].style.top = '5px';
  }

  this.addSeparator();

  this.addButton('geIcon geSprite geSprite-code', mxResources.get('html'), function () {
    graph.cellEditor.toggleViewMode();

    if (
      graph.cellEditor.textarea.innerHTML.length > 0 &&
      (graph.cellEditor.textarea.innerHTML != '&nbsp;' || !graph.cellEditor.clearOnChange)
    ) {
      window.setTimeout(function () {
        document.execCommand('selectAll', false, null);
      });
    }
  });

  this.addSeparator();

  var insertMenu = this.addMenuFunction(
    '',
    mxResources.get('insert'),
    true,
    mxUtils.bind(this, function (menu) {
      menu.addItem(
        mxResources.get('insertLink'),
        null,
        mxUtils.bind(this, function () {
          this.editorUi.actions.get('link').funct();
        })
      );

      menu.addItem(
        mxResources.get('insertImage'),
        null,
        mxUtils.bind(this, function () {
          this.editorUi.actions.get('image').funct();
        })
      );

      menu.addItem(
        mxResources.get('insertHorizontalRule'),
        null,
        mxUtils.bind(this, function () {
          document.execCommand('inserthorizontalrule', false, null);
        })
      );
    })
  );

  insertMenu.style.whiteSpace = 'nowrap';
  insertMenu.style.overflow = 'hidden';
  insertMenu.style.position = 'relative';
  insertMenu.innerHTML =
    '<div class="geSprite geSprite-plus" style="margin-left:-4px;margin-top:-3px;"></div>' + this.dropdownImageHtml;
  insertMenu.style.width = mxClient.IS_QUIRKS ? '36px' : '16px';

  // Fix for item size in kennedy theme
  if (EditorUi.compactUi) {
    insertMenu.getElementsByTagName('img')[0].style.left = '24px';
    insertMenu.getElementsByTagName('img')[0].style.top = '5px';
    insertMenu.style.width = mxClient.IS_QUIRKS ? '50px' : '30px';
  }

  this.addSeparator();

  // KNOWN: All table stuff does not work with undo/redo
  // KNOWN: Lost focus after click on submenu with text (not icon) in quirks and IE8. This is because the TD seems
  // to catch the focus on click in these browsers. NOTE: Workaround in mxPopupMenu for icon items (without text).
  var elt = this.addMenuFunction(
    'geIcon geSprite geSprite-table',
    mxResources.get('table'),
    false,
    mxUtils.bind(this, function (menu) {
      var elt = graph.getSelectedElement();
      var cell = graph.getParentByNames(elt, ['TD', 'TH'], graph.cellEditor.text2);
      var row = graph.getParentByName(elt, 'TR', graph.cellEditor.text2);

      if (row == null) {
        function createTable(rows, cols) {
          var html = ['<table>'];

          for (var i = 0; i < rows; i++) {
            html.push('<tr>');

            for (var j = 0; j < cols; j++) {
              html.push('<td><br></td>');
            }

            html.push('</tr>');
          }

          html.push('</table>');

          return html.join('');
        }

        this.editorUi.menus.addInsertTableItem(menu);
      } else {
        var table = graph.getParentByName(row, 'TABLE', graph.cellEditor.text2);

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            try {
              graph.selectNode(graph.insertColumn(table, cell != null ? cell.cellIndex : 0));
            } catch (e) {
              this.editorUi.handleError(e);
            }
          }),
          null,
          'geIcon geSprite geSprite-insertcolumnbefore'
        );
        elt.setAttribute('title', mxResources.get('insertColumnBefore'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            try {
              graph.selectNode(graph.insertColumn(table, cell != null ? cell.cellIndex + 1 : -1));
            } catch (e) {
              this.editorUi.handleError(e);
            }
          }),
          null,
          'geIcon geSprite geSprite-insertcolumnafter'
        );
        elt.setAttribute('title', mxResources.get('insertColumnAfter'));

        elt = menu.addItem(
          'Delete column',
          null,
          mxUtils.bind(this, function () {
            if (cell != null) {
              try {
                graph.deleteColumn(table, cell.cellIndex);
              } catch (e) {
                this.editorUi.handleError(e);
              }
            }
          }),
          null,
          'geIcon geSprite geSprite-deletecolumn'
        );
        elt.setAttribute('title', mxResources.get('deleteColumn'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            try {
              graph.selectNode(graph.insertRow(table, row.sectionRowIndex));
            } catch (e) {
              this.editorUi.handleError(e);
            }
          }),
          null,
          'geIcon geSprite geSprite-insertrowbefore'
        );
        elt.setAttribute('title', mxResources.get('insertRowBefore'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            try {
              graph.selectNode(graph.insertRow(table, row.sectionRowIndex + 1));
            } catch (e) {
              this.editorUi.handleError(e);
            }
          }),
          null,
          'geIcon geSprite geSprite-insertrowafter'
        );
        elt.setAttribute('title', mxResources.get('insertRowAfter'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            try {
              graph.deleteRow(table, row.sectionRowIndex);
            } catch (e) {
              this.editorUi.handleError(e);
            }
          }),
          null,
          'geIcon geSprite geSprite-deleterow'
        );
        elt.setAttribute('title', mxResources.get('deleteRow'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            // Converts rgb(r,g,b) values
            var color = table.style.borderColor.replace(
              /\brgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/g,
              function ($0, $1, $2, $3) {
                return (
                  '#' +
                  ('0' + Number($1).toString(16)).substr(-2) +
                  ('0' + Number($2).toString(16)).substr(-2) +
                  ('0' + Number($3).toString(16)).substr(-2)
                );
              }
            );
            this.editorUi.pickColor(color, function (newColor) {
              if (newColor == null || newColor == mxConstants.NONE) {
                table.removeAttribute('border');
                table.style.border = '';
                table.style.borderCollapse = '';
              } else {
                table.setAttribute('border', '1');
                table.style.border = '1px solid ' + newColor;
                table.style.borderCollapse = 'collapse';
              }
            });
          }),
          null,
          'geIcon geSprite geSprite-strokecolor'
        );
        elt.setAttribute('title', mxResources.get('borderColor'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            // Converts rgb(r,g,b) values
            var color = table.style.backgroundColor.replace(
              /\brgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/g,
              function ($0, $1, $2, $3) {
                return (
                  '#' +
                  ('0' + Number($1).toString(16)).substr(-2) +
                  ('0' + Number($2).toString(16)).substr(-2) +
                  ('0' + Number($3).toString(16)).substr(-2)
                );
              }
            );
            this.editorUi.pickColor(color, function (newColor) {
              if (newColor == null || newColor == mxConstants.NONE) {
                table.style.backgroundColor = '';
              } else {
                table.style.backgroundColor = newColor;
              }
            });
          }),
          null,
          'geIcon geSprite geSprite-fillcolor'
        );
        elt.setAttribute('title', mxResources.get('backgroundColor'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            var value = table.getAttribute('cellPadding') || 0;

            var dlg = new FilenameDialog(
              this.editorUi,
              value,
              mxResources.get('apply'),
              mxUtils.bind(this, function (newValue) {
                if (newValue != null && newValue.length > 0) {
                  table.setAttribute('cellPadding', newValue);
                } else {
                  table.removeAttribute('cellPadding');
                }
              }),
              mxResources.get('spacing')
            );
            this.editorUi.showDialog(dlg.container, 300, 80, true, true);
            dlg.init();
          }),
          null,
          'geIcon geSprite geSprite-fit'
        );
        elt.setAttribute('title', mxResources.get('spacing'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            table.setAttribute('align', 'left');
          }),
          null,
          'geIcon geSprite geSprite-left'
        );
        elt.setAttribute('title', mxResources.get('left'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            table.setAttribute('align', 'center');
          }),
          null,
          'geIcon geSprite geSprite-center'
        );
        elt.setAttribute('title', mxResources.get('center'));

        elt = menu.addItem(
          '',
          null,
          mxUtils.bind(this, function () {
            table.setAttribute('align', 'right');
          }),
          null,
          'geIcon geSprite geSprite-right'
        );
        elt.setAttribute('title', mxResources.get('right'));
      }
    })
  );

  elt.style.position = 'relative';
  elt.style.whiteSpace = 'nowrap';
  elt.style.overflow = 'hidden';
  elt.innerHTML = '<div class="geSprite geSprite-table" style="margin-left:-2px;"></div>' + this.dropdownImageHtml;
  elt.style.width = mxClient.IS_QUIRKS ? '50px' : '30px';

  // Fix for item size in kennedy theme
  if (EditorUi.compactUi) {
    elt.getElementsByTagName('img')[0].style.left = '22px';
    elt.getElementsByTagName('img')[0].style.top = '5px';
  }
};

/**
 * Hides the current menu.
 */
Toolbar.prototype.hideMenu = function () {
  this.editorUi.hideCurrentMenu();
};

/**
 * Adds a label to the toolbar.
 */
Toolbar.prototype.addMenu = function (label, tooltip, showLabels, name, c, showAll, ignoreState) {
  var menu = this.editorUi.menus.get(name);
  var elt = this.addMenuFunction(
    label,
    tooltip,
    showLabels,
    function () {
      menu.funct.apply(menu, arguments);
    },
    c,
    showAll
  );

  // Workaround for possible not a function
  // when extending HTML objects
  if (!ignoreState && typeof elt.setEnabled === 'function') {
    menu.addListener('stateChanged', function () {
      elt.setEnabled(menu.enabled);
    });
  }

  return elt;
};

/**
 * Adds a label to the toolbar.
 */
Toolbar.prototype.addMenuFunction = function (label, tooltip, showLabels, funct, c, showAll) {
  return this.addMenuFunctionInContainer(c != null ? c : this.container, label, tooltip, showLabels, funct, showAll);
};

/**
 * Adds a label to the toolbar.
 */
Toolbar.prototype.addMenuFunctionInContainer = function (container, label, tooltip, showLabels, funct, showAll) {
  var elt = showLabels ? this.createLabel(label) : this.createButton(label);
  this.initElement(elt, tooltip);
  this.addMenuHandler(elt, showLabels, funct, showAll);
  container.appendChild(elt);

  return elt;
};

/**
 * Adds a separator to the separator.
 */
Toolbar.prototype.addSeparator = function (c) {
  c = c != null ? c : this.container;
  var elt = document.createElement('div');
  elt.className = 'geSeparator';
  c.appendChild(elt);

  return elt;
};

/**
 * Adds given action item
 */
Toolbar.prototype.addItems = function (keys, c, ignoreDisabled) {
  var items = [];

  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];

    if (key == '-') {
      items.push(this.addSeparator(c));
    } else {
      items.push(this.addItem('geSprite-' + key.toLowerCase(), key, c, ignoreDisabled));
    }
  }

  return items;
};

/**
 * Adds given action item
 */
Toolbar.prototype.addItem = function (sprite, key, c, ignoreDisabled) {
  var action = this.editorUi.actions.get(key);
  var elt = null;

  if (action != null) {
    var tooltip = action.label;

    if (action.shortcut != null) {
      tooltip += ' (' + action.shortcut + ')';
    }

    elt = this.addButton(sprite, tooltip, action.funct, c);

    // Workaround for possible not a function
    // when extending HTML objects
    if (!ignoreDisabled && typeof elt.setEnabled === 'function') {
      elt.setEnabled(action.enabled);

      action.addListener('stateChanged', function () {
        elt.setEnabled(action.enabled);
      });
    }
  }

  return elt;
};

/**
 * Adds a button to the toolbar.
 */
Toolbar.prototype.addButton = function (classname, tooltip, funct, c) {
  var elt = this.createButton(classname);
  c = c != null ? c : this.container;

  this.initElement(elt, tooltip);
  this.addClickHandler(elt, funct);
  c.appendChild(elt);

  return elt;
};

/**
 * Initializes the given toolbar element.
 */
Toolbar.prototype.initElement = function (elt, tooltip) {
  // Adds tooltip
  if (tooltip != null) {
    elt.setAttribute('title', tooltip);
  }

  this.addEnabledState(elt);
};

/**
 * Adds enabled state with setter to DOM node (avoids JS wrapper).
 */
Toolbar.prototype.addEnabledState = function (elt) {
  var classname = elt.className;

  elt.setEnabled = function (value) {
    elt.enabled = value;

    if (value) {
      elt.className = classname;
    } else {
      elt.className = classname + ' mxDisabled';
    }
  };

  elt.setEnabled(true);
};

/**
 * Adds enabled state with setter to DOM node (avoids JS wrapper).
 */
Toolbar.prototype.addClickHandler = function (elt, funct) {
  if (funct != null) {
    mxEvent.addListener(elt, 'click', function (evt) {
      if (elt.enabled) {
        funct(evt);
      }

      mxEvent.consume(evt);
    });

    // Prevents focus
    mxEvent.addListener(
      elt,
      mxClient.IS_POINTER ? 'pointerdown' : 'mousedown',
      mxUtils.bind(this, function (evt) {
        evt.preventDefault();
      })
    );
  }
};

/**
 * Creates and returns a new button.
 */
Toolbar.prototype.createButton = function (classname) {
  var elt = document.createElement('a');
  elt.className = 'geButton';

  var inner = document.createElement('div');

  if (classname != null) {
    inner.className = 'geSprite ' + classname;
  }

  elt.appendChild(inner);

  return elt;
};

/**
 * Creates and returns a new button.
 */
Toolbar.prototype.createLabel = function (label, tooltip) {
  var elt = document.createElement('a');
  elt.className = 'geLabel';
  mxUtils.write(elt, label);

  return elt;
};

/**
 * Adds a handler for showing a menu in the given element.
 */
Toolbar.prototype.addMenuHandler = function (elt, showLabels, funct, showAll) {
  if (funct != null) {
    var graph = this.editorUi.editor.graph;
    var menu = null;
    var show = true;

    mxEvent.addListener(
      elt,
      'click',
      mxUtils.bind(this, function (evt) {
        if (show && (elt.enabled == null || elt.enabled)) {
          graph.popupMenuHandler.hideMenu();
          menu = new mxPopupMenu(funct, this.editorUi.container);
          menu.div.className += ' geToolbarMenu';
          menu.showDisabled = showAll;
          menu.labels = showLabels;
          menu.autoExpand = true;

          var offset = mxUtils.getOffset(elt, null, true);
          // 60 是两个工具条的高度
          menu.popup(offset.x, offset.y + 60, null, evt);
          this.editorUi.setCurrentMenu(menu, elt);

          // Workaround for scrollbar hiding menu items
          if (!showLabels && menu.div.scrollHeight > menu.div.clientHeight) {
            menu.div.style.width = '40px';
          }

          menu.hideMenu = mxUtils.bind(this, function () {
            mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
            this.editorUi.resetCurrentMenu();
            menu.destroy();
          });

          // Extends destroy to reset global state
          menu.addListener(
            mxEvent.EVENT_HIDE,
            mxUtils.bind(this, function () {
              this.currentElt = null;
            })
          );
        }

        show = true;
        mxEvent.consume(evt);
      })
    );

    // Hides menu if already showing and prevents focus
    mxEvent.addListener(
      elt,
      mxClient.IS_POINTER ? 'pointerdown' : 'mousedown',
      mxUtils.bind(this, function (evt) {
        show = this.currentElt != elt;
        evt.preventDefault();
      })
    );
  }
};

/**
 * Adds a handler for showing a menu in the given element.
 */
Toolbar.prototype.destroy = function () {
  if (this.gestureHandler != null) {
    mxEvent.removeGestureListeners(document, this.gestureHandler);
    this.gestureHandler = null;
  }
};
/**
 * Copyright (c) 2006-2012, JGraph Ltd
 */
/**
 * Constructs a new open dialog.
 */
var OpenDialog = function () {
  var iframe = document.createElement('iframe');
  iframe.style.backgroundColor = 'transparent';
  iframe.allowTransparency = 'true';
  iframe.style.borderStyle = 'none';
  iframe.style.borderWidth = '0px';
  iframe.style.overflow = 'hidden';
  iframe.frameBorder = '0';

  // Adds padding as a workaround for box model in older IE versions
  var dx = mxClient.IS_VML && (document.documentMode == null || document.documentMode < 8) ? 20 : 0;

  iframe.setAttribute('width', (Editor.useLocalStorage ? 640 : 320) + dx + 'px');
  iframe.setAttribute('height', (Editor.useLocalStorage ? 480 : 220) + dx + 'px');
  iframe.setAttribute('src', OPEN_FORM);

  this.container = iframe;
};

/**
 * Constructs a new color dialog.
 */
var ColorDialog = function (editorUi, color, apply, cancelFn) {
  this.editorUi = editorUi;

  var input = document.createElement('input');
  input.style.marginBottom = '10px';
  input.style.width = '216px';

  // Required for picker to render in IE
  if (mxClient.IS_IE) {
    input.style.marginTop = '10px';
    document.body.appendChild(input);
  }

  var applyFunction = apply != null ? apply : this.createApplyFunction();

  function doApply() {
    var color = input.value;

    // Blocks any non-alphabetic chars in colors
    if (/(^#?[a-zA-Z0-9]*$)/.test(color)) {
      if (color != 'none' && color.charAt(0) != '#') {
        color = '#' + color;
      }

      ColorDialog.addRecentColor(color != 'none' ? color.substring(1) : color, 12);
      applyFunction(color);
      editorUi.hideDialog();
    } else {
      editorUi.handleError({ message: mxResources.get('invalidInput') });
    }
  }

  this.init = function () {
    if (!mxClient.IS_TOUCH) {
      input.focus();
    }
  };

  var picker = new mxJSColor.color(input);
  picker.pickerOnfocus = false;
  picker.showPicker();

  var div = document.createElement('div');
  mxJSColor.picker.box.style.position = 'relative';
  mxJSColor.picker.box.style.width = '230px';
  mxJSColor.picker.box.style.height = '100px';
  mxJSColor.picker.box.style.paddingBottom = '10px';
  div.appendChild(mxJSColor.picker.box);

  var center = document.createElement('center');

  function createRecentColorTable() {
    var table = addPresets(
      ColorDialog.recentColors.length == 0 ? ['FFFFFF'] : ColorDialog.recentColors,
      11,
      'FFFFFF',
      true
    );
    table.style.marginBottom = '8px';

    return table;
  }

  function addPresets(presets, rowLength, defaultColor, addResetOption) {
    rowLength = rowLength != null ? rowLength : 12;
    var table = document.createElement('table');
    table.style.borderCollapse = 'collapse';
    table.setAttribute('cellspacing', '0');
    table.style.marginBottom = '20px';
    table.style.cellSpacing = '0px';
    var tbody = document.createElement('tbody');
    table.appendChild(tbody);

    var rows = presets.length / rowLength;

    for (var row = 0; row < rows; row++) {
      var tr = document.createElement('tr');

      for (var i = 0; i < rowLength; i++) {
        (function (clr) {
          var td = document.createElement('td');
          td.style.border = '1px solid black';
          td.style.padding = '0px';
          td.style.width = '16px';
          td.style.height = '16px';

          if (clr == null) {
            clr = defaultColor;
          }

          if (clr == 'none') {
            td.style.background = "url('" + Dialog.prototype.noColorImage + "')";
          } else {
            td.style.backgroundColor = '#' + clr;
          }

          tr.appendChild(td);

          if (clr != null) {
            td.style.cursor = 'pointer';

            mxEvent.addListener(td, 'click', function () {
              if (clr == 'none') {
                picker.fromString('ffffff');
                input.value = 'none';
              } else {
                picker.fromString(clr);
              }
            });

            mxEvent.addListener(td, 'dblclick', doApply);
          }
        })(presets[row * rowLength + i]);
      }

      tbody.appendChild(tr);
    }

    if (addResetOption) {
      var td = document.createElement('td');
      td.setAttribute('title', mxResources.get('reset'));
      td.style.border = '1px solid black';
      td.style.padding = '0px';
      td.style.width = '16px';
      td.style.height = '16px';
      td.style.backgroundImage = "url('" + Dialog.prototype.closeImage + "')";
      td.style.backgroundPosition = 'center center';
      td.style.backgroundRepeat = 'no-repeat';
      td.style.cursor = 'pointer';

      tr.appendChild(td);

      mxEvent.addListener(td, 'click', function () {
        ColorDialog.resetRecentColors();
        table.parentNode.replaceChild(createRecentColorTable(), table);
      });
    }

    center.appendChild(table);

    return table;
  }

  div.appendChild(input);
  mxUtils.br(div);

  // Adds recent colors
  createRecentColorTable();

  // Adds presets
  var table = addPresets(this.presetColors);
  table.style.marginBottom = '8px';
  table = addPresets(this.defaultColors);
  table.style.marginBottom = '16px';

  div.appendChild(center);

  var buttons = document.createElement('div');
  buttons.style.textAlign = 'right';
  buttons.style.whiteSpace = 'nowrap';

  var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
    editorUi.hideDialog();

    if (cancelFn != null) {
      cancelFn();
    }
  });
  cancelBtn.className = 'geBtn';

  if (editorUi.editor.cancelFirst) {
    buttons.appendChild(cancelBtn);
  }

  var applyBtn = mxUtils.button(mxResources.get('apply'), doApply);
  applyBtn.className = 'geBtn gePrimaryBtn';
  buttons.appendChild(applyBtn);

  if (!editorUi.editor.cancelFirst) {
    buttons.appendChild(cancelBtn);
  }

  if (color != null) {
    if (color == 'none') {
      picker.fromString('ffffff');
      input.value = 'none';
    } else {
      picker.fromString(color);
    }
  }

  div.appendChild(buttons);
  this.picker = picker;
  this.colorInput = input;

  // LATER: Only fires if input if focused, should always
  // fire if this dialog is showing.
  mxEvent.addListener(div, 'keydown', function (e) {
    if (e.keyCode == 27) {
      editorUi.hideDialog();

      if (cancelFn != null) {
        cancelFn();
      }

      mxEvent.consume(e);
    }
  });

  this.container = div;
};

/**
 * Creates function to apply value
 */
ColorDialog.prototype.presetColors = [
  'E6D0DE',
  'CDA2BE',
  'B5739D',
  'E1D5E7',
  'C3ABD0',
  'A680B8',
  'D4E1F5',
  'A9C4EB',
  '7EA6E0',
  'D5E8D4',
  '9AC7BF',
  '67AB9F',
  'D5E8D4',
  'B9E0A5',
  '97D077',
  'FFF2CC',
  'FFE599',
  'FFD966',
  'FFF4C3',
  'FFCE9F',
  'FFB570',
  'F8CECC',
  'F19C99',
  'EA6B66',
];

/**
 * Creates function to apply value
 */
ColorDialog.prototype.defaultColors = [
  'none',
  'FFFFFF',
  'E6E6E6',
  'CCCCCC',
  'B3B3B3',
  '999999',
  '808080',
  '666666',
  '4D4D4D',
  '333333',
  '1A1A1A',
  '000000',
  'FFCCCC',
  'FFE6CC',
  'FFFFCC',
  'E6FFCC',
  'CCFFCC',
  'CCFFE6',
  'CCFFFF',
  'CCE5FF',
  'CCCCFF',
  'E5CCFF',
  'FFCCFF',
  'FFCCE6',
  'FF9999',
  'FFCC99',
  'FFFF99',
  'CCFF99',
  '99FF99',
  '99FFCC',
  '99FFFF',
  '99CCFF',
  '9999FF',
  'CC99FF',
  'FF99FF',
  'FF99CC',
  'FF6666',
  'FFB366',
  'FFFF66',
  'B3FF66',
  '66FF66',
  '66FFB3',
  '66FFFF',
  '66B2FF',
  '6666FF',
  'B266FF',
  'FF66FF',
  'FF66B3',
  'FF3333',
  'FF9933',
  'FFFF33',
  '99FF33',
  '33FF33',
  '33FF99',
  '33FFFF',
  '3399FF',
  '3333FF',
  '9933FF',
  'FF33FF',
  'FF3399',
  'FF0000',
  'FF8000',
  'FFFF00',
  '80FF00',
  '00FF00',
  '00FF80',
  '00FFFF',
  '007FFF',
  '0000FF',
  '7F00FF',
  'FF00FF',
  'FF0080',
  'CC0000',
  'CC6600',
  'CCCC00',
  '66CC00',
  '00CC00',
  '00CC66',
  '00CCCC',
  '0066CC',
  '0000CC',
  '6600CC',
  'CC00CC',
  'CC0066',
  '990000',
  '994C00',
  '999900',
  '4D9900',
  '009900',
  '00994D',
  '009999',
  '004C99',
  '000099',
  '4C0099',
  '990099',
  '99004D',
  '660000',
  '663300',
  '666600',
  '336600',
  '006600',
  '006633',
  '006666',
  '003366',
  '000066',
  '330066',
  '660066',
  '660033',
  '330000',
  '331A00',
  '333300',
  '1A3300',
  '003300',
  '00331A',
  '003333',
  '001933',
  '000033',
  '190033',
  '330033',
  '33001A',
];

/**
 * Creates function to apply value
 */
ColorDialog.prototype.createApplyFunction = function () {
  return mxUtils.bind(this, function (color) {
    var graph = this.editorUi.editor.graph;

    graph.getModel().beginUpdate();
    try {
      graph.setCellStyles(this.currentColorKey, color);
      this.editorUi.fireEvent(
        new mxEventObject(
          'styleChanged',
          'keys',
          [this.currentColorKey],
          'values',
          [color],
          'cells',
          graph.getSelectionCells()
        )
      );
    } finally {
      graph.getModel().endUpdate();
    }
  });
};

/**
 *
 */
ColorDialog.recentColors = [];

/**
 * Adds recent color for later use.
 */
ColorDialog.addRecentColor = function (color, max) {
  if (color != null) {
    mxUtils.remove(color, ColorDialog.recentColors);
    ColorDialog.recentColors.splice(0, 0, color);

    if (ColorDialog.recentColors.length >= max) {
      ColorDialog.recentColors.pop();
    }
  }
};

/**
 * Adds recent color for later use.
 */
ColorDialog.resetRecentColors = function () {
  ColorDialog.recentColors = [];
};

/**
 * Constructs a new about dialog.
 */
var AboutDialog = function (editorUi) {
  var div = document.createElement('div');
  div.setAttribute('align', 'center');
  var h3 = document.createElement('h3');
  mxUtils.write(h3, mxResources.get('about') + ' GraphEditor');
  div.appendChild(h3);
  var img = document.createElement('img');
  img.style.border = '0px';
  img.setAttribute('width', '176');
  img.setAttribute('width', '151');
  img.setAttribute('src', IMAGE_PATH + '/logo.png');
  div.appendChild(img);
  mxUtils.br(div);
  mxUtils.write(div, 'Powered by mxGraph ' + mxClient.VERSION);
  mxUtils.br(div);
  var link = document.createElement('a');
  link.setAttribute('href', 'http://www.jgraph.com/');
  link.setAttribute('target', '_blank');
  mxUtils.write(link, 'www.jgraph.com');
  div.appendChild(link);
  mxUtils.br(div);
  mxUtils.br(div);
  var closeBtn = mxUtils.button(mxResources.get('close'), function () {
    editorUi.hideDialog();
  });
  closeBtn.className = 'geBtn gePrimaryBtn';
  div.appendChild(closeBtn);

  this.container = div;
};

/**
 * Constructs a new textarea dialog.
 */
var TextareaDialog = function (
  editorUi,
  title,
  url,
  fn,
  cancelFn,
  cancelTitle,
  w,
  h,
  addButtons,
  noHide,
  noWrap,
  applyTitle,
  helpLink,
  customButtons
) {
  w = w != null ? w : 300;
  h = h != null ? h : 120;
  noHide = noHide != null ? noHide : false;
  var row, td;

  var table = document.createElement('table');
  var tbody = document.createElement('tbody');

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.fontSize = '10pt';
  td.style.width = '100px';
  mxUtils.write(td, title);

  row.appendChild(td);
  tbody.appendChild(row);

  row = document.createElement('tr');
  td = document.createElement('td');

  var nameInput = document.createElement('textarea');

  if (noWrap) {
    nameInput.setAttribute('wrap', 'off');
  }

  nameInput.setAttribute('spellcheck', 'false');
  nameInput.setAttribute('autocorrect', 'off');
  nameInput.setAttribute('autocomplete', 'off');
  nameInput.setAttribute('autocapitalize', 'off');

  mxUtils.write(nameInput, url || '');
  nameInput.style.resize = 'none';
  nameInput.style.width = w + 'px';
  nameInput.style.height = h + 'px';

  this.textarea = nameInput;

  this.init = function () {
    nameInput.focus();
    nameInput.scrollTop = 0;
  };

  td.appendChild(nameInput);
  row.appendChild(td);

  tbody.appendChild(row);

  row = document.createElement('tr');
  td = document.createElement('td');
  td.style.paddingTop = '14px';
  td.style.whiteSpace = 'nowrap';
  td.setAttribute('align', 'right');

  if (helpLink != null) {
    var helpBtn = mxUtils.button(mxResources.get('help'), function () {
      editorUi.editor.graph.openLink(helpLink);
    });
    helpBtn.className = 'geBtn';

    td.appendChild(helpBtn);
  }

  if (customButtons != null) {
    for (var i = 0; i < customButtons.length; i++) {
      (function (label, fn) {
        var customBtn = mxUtils.button(label, function (e) {
          fn(e, nameInput);
        });
        customBtn.className = 'geBtn';

        td.appendChild(customBtn);
      })(customButtons[i][0], customButtons[i][1]);
    }
  }

  var cancelBtn = mxUtils.button(cancelTitle || mxResources.get('cancel'), function () {
    editorUi.hideDialog();

    if (cancelFn != null) {
      cancelFn();
    }
  });
  cancelBtn.className = 'geBtn';

  if (editorUi.editor.cancelFirst) {
    td.appendChild(cancelBtn);
  }

  if (addButtons != null) {
    addButtons(td, nameInput);
  }

  if (fn != null) {
    var genericBtn = mxUtils.button(applyTitle || mxResources.get('apply'), function () {
      if (!noHide) {
        editorUi.hideDialog();
      }

      fn(nameInput.value);
    });

    genericBtn.className = 'geBtn gePrimaryBtn';
    td.appendChild(genericBtn);
  }

  if (!editorUi.editor.cancelFirst) {
    td.appendChild(cancelBtn);
  }

  row.appendChild(td);
  tbody.appendChild(row);
  table.appendChild(tbody);
  this.container = table;
};

/**
 * Constructs a new edit file dialog.
 */
var EditDiagramDialog = function (editorUi) {
  var div = document.createElement('div');
  div.style.textAlign = 'right';
  var textarea = document.createElement('textarea');
  textarea.setAttribute('wrap', 'off');
  textarea.setAttribute('spellcheck', 'false');
  textarea.setAttribute('autocorrect', 'off');
  textarea.setAttribute('autocomplete', 'off');
  textarea.setAttribute('autocapitalize', 'off');
  textarea.style.overflow = 'auto';
  textarea.style.resize = 'none';
  textarea.style.width = '600px';
  textarea.style.height = '360px';
  textarea.style.marginBottom = '16px';

  textarea.value = mxUtils.getPrettyXml(editorUi.editor.getGraphXml());
  div.appendChild(textarea);

  this.init = function () {
    textarea.focus();
  };

  // Enables dropping files
  if (Graph.fileSupport) {
    function handleDrop(evt) {
      evt.stopPropagation();
      evt.preventDefault();

      if (evt.dataTransfer.files.length > 0) {
        var file = evt.dataTransfer.files[0];
        var reader = new FileReader();

        reader.onload = function (e) {
          textarea.value = e.target.result;
        };

        reader.readAsText(file);
      } else {
        textarea.value = editorUi.extractGraphModelFromEvent(evt);
      }
    }

    function handleDragOver(evt) {
      evt.stopPropagation();
      evt.preventDefault();
    }

    // Setup the dnd listeners.
    textarea.addEventListener('dragover', handleDragOver, false);
    textarea.addEventListener('drop', handleDrop, false);
  }

  var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
    editorUi.hideDialog();
  });
  cancelBtn.className = 'geBtn';

  if (editorUi.editor.cancelFirst) {
    div.appendChild(cancelBtn);
  }

  var select = document.createElement('select');
  select.style.width = '180px';
  select.className = 'geBtn';

  if (editorUi.editor.graph.isEnabled()) {
    var replaceOption = document.createElement('option');
    replaceOption.setAttribute('value', 'replace');
    mxUtils.write(replaceOption, mxResources.get('replaceExistingDrawing'));
    select.appendChild(replaceOption);
  }

  var newOption = document.createElement('option');
  newOption.setAttribute('value', 'new');
  mxUtils.write(newOption, mxResources.get('openInNewWindow'));

  if (EditDiagramDialog.showNewWindowOption) {
    select.appendChild(newOption);
  }

  if (editorUi.editor.graph.isEnabled()) {
    var importOption = document.createElement('option');
    importOption.setAttribute('value', 'import');
    mxUtils.write(importOption, mxResources.get('addToExistingDrawing'));
    select.appendChild(importOption);
  }

  div.appendChild(select);

  var okBtn = mxUtils.button(mxResources.get('ok'), function () {
    // Removes all illegal control characters before parsing
    var data = Graph.zapGremlins(mxUtils.trim(textarea.value));
    var error = null;

    if (select.value == 'new') {
      editorUi.hideDialog();
      editorUi.editor.editAsNew(data);
    } else if (select.value == 'replace') {
      editorUi.editor.graph.model.beginUpdate();
      try {
        editorUi.editor.setGraphXml(mxUtils.parseXml(data).documentElement);
        // LATER: Why is hideDialog between begin-/endUpdate faster?
        editorUi.hideDialog();
      } catch (e) {
        error = e;
      } finally {
        editorUi.editor.graph.model.endUpdate();
      }
    } else if (select.value == 'import') {
      editorUi.editor.graph.model.beginUpdate();
      try {
        var doc = mxUtils.parseXml(data);
        var model = new mxGraphModel();
        var codec = new mxCodec(doc);
        codec.decode(doc.documentElement, model);

        var children = model.getChildren(model.getChildAt(model.getRoot(), 0));
        editorUi.editor.graph.setSelectionCells(editorUi.editor.graph.importCells(children));

        // LATER: Why is hideDialog between begin-/endUpdate faster?
        editorUi.hideDialog();
      } catch (e) {
        error = e;
      } finally {
        editorUi.editor.graph.model.endUpdate();
      }
    }

    if (error != null) {
      mxUtils.alert(error.message);
    }
  });
  okBtn.className = 'geBtn gePrimaryBtn';
  div.appendChild(okBtn);

  if (!editorUi.editor.cancelFirst) {
    div.appendChild(cancelBtn);
  }

  this.container = div;
};

/**
 *
 */
EditDiagramDialog.showNewWindowOption = true;

/**
 * Constructs a new export dialog.
 */
var ExportDialog = function (editorUi) {
  var graph = editorUi.editor.graph;
  var bounds = graph.getGraphBounds();
  var scale = graph.view.scale;

  var width = Math.ceil(bounds.width / scale);
  var height = Math.ceil(bounds.height / scale);

  var row, td;

  var table = document.createElement('table');
  var tbody = document.createElement('tbody');
  table.setAttribute('cellpadding', mxClient.IS_SF ? '0' : '2');

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.fontSize = '10pt';
  td.style.width = '100px';
  mxUtils.write(td, mxResources.get('filename') + ':');

  row.appendChild(td);

  var nameInput = document.createElement('input');
  nameInput.setAttribute('value', editorUi.editor.getOrCreateFilename());
  nameInput.style.width = '180px';

  td = document.createElement('td');
  td.appendChild(nameInput);
  row.appendChild(td);

  tbody.appendChild(row);

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.fontSize = '10pt';
  mxUtils.write(td, mxResources.get('format') + ':');

  row.appendChild(td);

  var imageFormatSelect = document.createElement('select');
  imageFormatSelect.style.width = '180px';

  var pngOption = document.createElement('option');
  pngOption.setAttribute('value', 'png');
  mxUtils.write(pngOption, mxResources.get('formatPng'));
  imageFormatSelect.appendChild(pngOption);

  var gifOption = document.createElement('option');

  if (ExportDialog.showGifOption) {
    gifOption.setAttribute('value', 'gif');
    mxUtils.write(gifOption, mxResources.get('formatGif'));
    imageFormatSelect.appendChild(gifOption);
  }

  var jpgOption = document.createElement('option');
  jpgOption.setAttribute('value', 'jpg');
  mxUtils.write(jpgOption, mxResources.get('formatJpg'));
  imageFormatSelect.appendChild(jpgOption);

  var pdfOption = document.createElement('option');
  pdfOption.setAttribute('value', 'pdf');
  mxUtils.write(pdfOption, mxResources.get('formatPdf'));
  imageFormatSelect.appendChild(pdfOption);

  var svgOption = document.createElement('option');
  svgOption.setAttribute('value', 'svg');
  mxUtils.write(svgOption, mxResources.get('formatSvg'));
  imageFormatSelect.appendChild(svgOption);

  if (ExportDialog.showXmlOption) {
    var xmlOption = document.createElement('option');
    xmlOption.setAttribute('value', 'xml');
    mxUtils.write(xmlOption, mxResources.get('formatXml'));
    imageFormatSelect.appendChild(xmlOption);
  }

  td = document.createElement('td');
  td.appendChild(imageFormatSelect);
  row.appendChild(td);

  tbody.appendChild(row);

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.fontSize = '10pt';
  mxUtils.write(td, mxResources.get('zoom') + ' (%):');

  row.appendChild(td);

  var zoomInput = document.createElement('input');
  zoomInput.setAttribute('type', 'number');
  zoomInput.setAttribute('value', '100');
  zoomInput.style.width = '180px';

  td = document.createElement('td');
  td.appendChild(zoomInput);
  row.appendChild(td);

  tbody.appendChild(row);

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.fontSize = '10pt';
  mxUtils.write(td, mxResources.get('width') + ':');

  row.appendChild(td);

  var widthInput = document.createElement('input');
  widthInput.setAttribute('value', width);
  widthInput.style.width = '180px';

  td = document.createElement('td');
  td.appendChild(widthInput);
  row.appendChild(td);

  tbody.appendChild(row);

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.fontSize = '10pt';
  mxUtils.write(td, mxResources.get('height') + ':');

  row.appendChild(td);

  var heightInput = document.createElement('input');
  heightInput.setAttribute('value', height);
  heightInput.style.width = '180px';

  td = document.createElement('td');
  td.appendChild(heightInput);
  row.appendChild(td);

  tbody.appendChild(row);

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.fontSize = '10pt';
  mxUtils.write(td, mxResources.get('dpi') + ':');

  row.appendChild(td);

  var dpiSelect = document.createElement('select');
  dpiSelect.style.width = '180px';

  var dpi100Option = document.createElement('option');
  dpi100Option.setAttribute('value', '100');
  mxUtils.write(dpi100Option, '100dpi');
  dpiSelect.appendChild(dpi100Option);

  var dpi200Option = document.createElement('option');
  dpi200Option.setAttribute('value', '200');
  mxUtils.write(dpi200Option, '200dpi');
  dpiSelect.appendChild(dpi200Option);

  var dpi300Option = document.createElement('option');
  dpi300Option.setAttribute('value', '300');
  mxUtils.write(dpi300Option, '300dpi');
  dpiSelect.appendChild(dpi300Option);

  var dpi400Option = document.createElement('option');
  dpi400Option.setAttribute('value', '400');
  mxUtils.write(dpi400Option, '400dpi');
  dpiSelect.appendChild(dpi400Option);

  var dpiCustOption = document.createElement('option');
  dpiCustOption.setAttribute('value', 'custom');
  mxUtils.write(dpiCustOption, mxResources.get('custom'));
  dpiSelect.appendChild(dpiCustOption);

  var customDpi = document.createElement('input');
  customDpi.style.width = '180px';
  customDpi.style.display = 'none';
  customDpi.setAttribute('value', '100');
  customDpi.setAttribute('type', 'number');
  customDpi.setAttribute('min', '50');
  customDpi.setAttribute('step', '50');

  var zoomUserChanged = false;

  mxEvent.addListener(dpiSelect, 'change', function () {
    if (this.value == 'custom') {
      this.style.display = 'none';
      customDpi.style.display = '';
      customDpi.focus();
    } else {
      customDpi.value = this.value;

      if (!zoomUserChanged) {
        zoomInput.value = this.value;
      }
    }
  });

  mxEvent.addListener(customDpi, 'change', function () {
    var dpi = parseInt(customDpi.value);

    if (isNaN(dpi) || dpi <= 0) {
      customDpi.style.backgroundColor = 'red';
    } else {
      customDpi.style.backgroundColor = '';

      if (!zoomUserChanged) {
        zoomInput.value = dpi;
      }
    }
  });

  td = document.createElement('td');
  td.appendChild(dpiSelect);
  td.appendChild(customDpi);
  row.appendChild(td);

  tbody.appendChild(row);

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.fontSize = '10pt';
  mxUtils.write(td, mxResources.get('background') + ':');

  row.appendChild(td);

  var transparentCheckbox = document.createElement('input');
  transparentCheckbox.setAttribute('type', 'checkbox');
  transparentCheckbox.checked = graph.background == null || graph.background == mxConstants.NONE;

  td = document.createElement('td');
  td.appendChild(transparentCheckbox);
  mxUtils.write(td, mxResources.get('transparent'));

  row.appendChild(td);

  tbody.appendChild(row);

  row = document.createElement('tr');

  td = document.createElement('td');
  td.style.fontSize = '10pt';
  mxUtils.write(td, mxResources.get('borderWidth') + ':');

  row.appendChild(td);

  var borderInput = document.createElement('input');
  borderInput.setAttribute('type', 'number');
  borderInput.setAttribute('value', ExportDialog.lastBorderValue);
  borderInput.style.width = '180px';

  td = document.createElement('td');
  td.appendChild(borderInput);
  row.appendChild(td);

  tbody.appendChild(row);
  table.appendChild(tbody);

  // Handles changes in the export format
  function formatChanged() {
    var name = nameInput.value;
    var dot = name.lastIndexOf('.');

    if (dot > 0) {
      nameInput.value = name.substring(0, dot + 1) + imageFormatSelect.value;
    } else {
      nameInput.value = name + '.' + imageFormatSelect.value;
    }

    if (imageFormatSelect.value === 'xml') {
      zoomInput.setAttribute('disabled', 'true');
      widthInput.setAttribute('disabled', 'true');
      heightInput.setAttribute('disabled', 'true');
      borderInput.setAttribute('disabled', 'true');
    } else {
      zoomInput.removeAttribute('disabled');
      widthInput.removeAttribute('disabled');
      heightInput.removeAttribute('disabled');
      borderInput.removeAttribute('disabled');
    }

    if (imageFormatSelect.value === 'png' || imageFormatSelect.value === 'svg' || imageFormatSelect.value === 'pdf') {
      transparentCheckbox.removeAttribute('disabled');
    } else {
      transparentCheckbox.setAttribute('disabled', 'disabled');
    }

    if (imageFormatSelect.value === 'png') {
      dpiSelect.removeAttribute('disabled');
      customDpi.removeAttribute('disabled');
    } else {
      dpiSelect.setAttribute('disabled', 'disabled');
      customDpi.setAttribute('disabled', 'disabled');
    }
  }

  mxEvent.addListener(imageFormatSelect, 'change', formatChanged);
  formatChanged();

  function checkValues() {
    if (widthInput.value * heightInput.value > MAX_AREA || widthInput.value <= 0) {
      widthInput.style.backgroundColor = 'red';
    } else {
      widthInput.style.backgroundColor = '';
    }

    if (widthInput.value * heightInput.value > MAX_AREA || heightInput.value <= 0) {
      heightInput.style.backgroundColor = 'red';
    } else {
      heightInput.style.backgroundColor = '';
    }
  }

  mxEvent.addListener(zoomInput, 'change', function () {
    zoomUserChanged = true;
    var s = Math.max(0, parseFloat(zoomInput.value) || 100) / 100;
    zoomInput.value = parseFloat((s * 100).toFixed(2));

    if (width > 0) {
      widthInput.value = Math.floor(width * s);
      heightInput.value = Math.floor(height * s);
    } else {
      zoomInput.value = '100';
      widthInput.value = width;
      heightInput.value = height;
    }

    checkValues();
  });

  mxEvent.addListener(widthInput, 'change', function () {
    var s = parseInt(widthInput.value) / width;

    if (s > 0) {
      zoomInput.value = parseFloat((s * 100).toFixed(2));
      heightInput.value = Math.floor(height * s);
    } else {
      zoomInput.value = '100';
      widthInput.value = width;
      heightInput.value = height;
    }

    checkValues();
  });

  mxEvent.addListener(heightInput, 'change', function () {
    var s = parseInt(heightInput.value) / height;

    if (s > 0) {
      zoomInput.value = parseFloat((s * 100).toFixed(2));
      widthInput.value = Math.floor(width * s);
    } else {
      zoomInput.value = '100';
      widthInput.value = width;
      heightInput.value = height;
    }

    checkValues();
  });

  row = document.createElement('tr');
  td = document.createElement('td');
  td.setAttribute('align', 'right');
  td.style.paddingTop = '22px';
  td.colSpan = 2;

  var saveBtn = mxUtils.button(
    mxResources.get('export'),
    mxUtils.bind(this, function () {
      if (parseInt(zoomInput.value) <= 0) {
        mxUtils.alert(mxResources.get('drawingEmpty'));
      } else {
        var name = nameInput.value;
        var format = imageFormatSelect.value;
        var s = Math.max(0, parseFloat(zoomInput.value) || 100) / 100;
        var b = Math.max(0, parseInt(borderInput.value));
        var bg = graph.background;
        var dpi = Math.max(1, parseInt(customDpi.value));

        if ((format == 'svg' || format == 'png' || format == 'pdf') && transparentCheckbox.checked) {
          bg = null;
        } else if (bg == null || bg == mxConstants.NONE) {
          bg = '#ffffff';
        }

        ExportDialog.lastBorderValue = b;
        ExportDialog.exportFile(editorUi, name, format, bg, s, b, dpi);
      }
    })
  );
  saveBtn.className = 'geBtn gePrimaryBtn';

  var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
    editorUi.hideDialog();
  });
  cancelBtn.className = 'geBtn';

  if (editorUi.editor.cancelFirst) {
    td.appendChild(cancelBtn);
    td.appendChild(saveBtn);
  } else {
    td.appendChild(saveBtn);
    td.appendChild(cancelBtn);
  }

  row.appendChild(td);
  tbody.appendChild(row);
  table.appendChild(tbody);
  this.container = table;
};

/**
 * Remembers last value for border.
 */
ExportDialog.lastBorderValue = 0;

/**
 * Global switches for the export dialog.
 */
ExportDialog.showGifOption = true;

/**
 * Global switches for the export dialog.
 */
ExportDialog.showXmlOption = true;

/**
 * Hook for getting the export format. Returns null for the default
 * intermediate XML export format or a function that returns the
 * parameter and value to be used in the request in the form
 * key=value, where value should be URL encoded.
 */
ExportDialog.exportFile = function (editorUi, name, format, bg, s, b, dpi) {
  var graph = editorUi.editor.graph;

  if (format == 'xml') {
    ExportDialog.saveLocalFile(editorUi, mxUtils.getXml(editorUi.editor.getGraphXml()), name, format);
  } else if (format == 'svg') {
    ExportDialog.saveLocalFile(editorUi, mxUtils.getXml(graph.getSvg(bg, s, b)), name, format);
  } else {
    var bounds = graph.getGraphBounds();

    // New image export
    var xmlDoc = mxUtils.createXmlDocument();
    var root = xmlDoc.createElement('output');
    xmlDoc.appendChild(root);

    // Renders graph. Offset will be multiplied with state's scale when painting state.
    var xmlCanvas = new mxXmlCanvas2D(root);
    xmlCanvas.translate(
      Math.floor((b / s - bounds.x) / graph.view.scale),
      Math.floor((b / s - bounds.y) / graph.view.scale)
    );
    xmlCanvas.scale(s / graph.view.scale);

    var imgExport = new mxImageExport();
    imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);

    // Puts request data together
    var param = 'xml=' + encodeURIComponent(mxUtils.getXml(root));
    var w = Math.ceil((bounds.width * s) / graph.view.scale + 2 * b);
    var h = Math.ceil((bounds.height * s) / graph.view.scale + 2 * b);

    // Requests image if request is valid
    if (param.length <= MAX_REQUEST_SIZE && w * h < MAX_AREA) {
      editorUi.hideDialog();
      var req = new mxXmlRequest(
        EXPORT_URL,
        'format=' +
          format +
          '&filename=' +
          encodeURIComponent(name) +
          '&bg=' +
          (bg != null ? bg : 'none') +
          '&w=' +
          w +
          '&h=' +
          h +
          '&' +
          param +
          '&dpi=' +
          dpi
      );
      req.simulate(document, '_blank');
    } else {
      mxUtils.alert(mxResources.get('drawingTooLarge'));
    }
  }
};

/**
 * Hook for getting the export format. Returns null for the default
 * intermediate XML export format or a function that returns the
 * parameter and value to be used in the request in the form
 * key=value, where value should be URL encoded.
 */
ExportDialog.saveLocalFile = function (editorUi, data, filename, format) {
  if (data.length < MAX_REQUEST_SIZE) {
    editorUi.hideDialog();
    var req = new mxXmlRequest(
      SAVE_URL,
      'xml=' + encodeURIComponent(data) + '&filename=' + encodeURIComponent(filename) + '&format=' + format
    );
    req.simulate(document, '_blank');
  } else {
    mxUtils.alert(mxResources.get('drawingTooLarge'));
    mxUtils.popup(xml);
  }
};

/**
 * Constructs a new metadata dialog.
 */
var EditDataDialog = function (ui, cell) {
  var div = document.createElement('div');
  var graph = ui.editor.graph;

  var value = graph.getModel().getValue(cell);

  // Converts the value to an XML node
  if (!mxUtils.isNode(value)) {
    var doc = mxUtils.createXmlDocument();
    var obj = doc.createElement('object');
    obj.setAttribute('label', value || '');
    value = obj;
  }

  var meta = {};

  try {
    var temp = mxUtils.getValue(ui.editor.graph.getCurrentCellStyle(cell), 'metaData', null);

    if (temp != null) {
      meta = JSON.parse(temp);
    }
  } catch (e) {
    // ignore
  }

  // Creates the dialog contents
  var form = new mxForm('properties');
  form.table.style.width = '100%';

  var attrs = value.attributes;
  var names = [];
  var texts = [];
  var count = 0;

  var id = EditDataDialog.getDisplayIdForCell != null ? EditDataDialog.getDisplayIdForCell(ui, cell) : null;

  var addRemoveButton = function (text, name) {
    var wrapper = document.createElement('div');
    wrapper.style.position = 'relative';
    wrapper.style.paddingRight = '20px';
    wrapper.style.boxSizing = 'border-box';
    wrapper.style.width = '100%';

    var removeAttr = document.createElement('a');
    var img = mxUtils.createImage(Dialog.prototype.closeImage);
    img.style.height = '9px';
    img.style.fontSize = '9px';
    img.style.marginBottom = mxClient.IS_IE11 ? '-1px' : '5px';

    removeAttr.className = 'geButton';
    removeAttr.setAttribute('title', mxResources.get('delete'));
    removeAttr.style.position = 'absolute';
    removeAttr.style.top = '4px';
    removeAttr.style.right = '0px';
    removeAttr.style.margin = '0px';
    removeAttr.style.width = '9px';
    removeAttr.style.height = '9px';
    removeAttr.style.cursor = 'pointer';
    removeAttr.appendChild(img);

    var removeAttrFn = (function (name) {
      return function () {
        var count = 0;

        for (var j = 0; j < names.length; j++) {
          if (names[j] == name) {
            texts[j] = null;
            form.table.deleteRow(count + (id != null ? 1 : 0));

            break;
          }

          if (texts[j] != null) {
            count++;
          }
        }
      };
    })(name);

    mxEvent.addListener(removeAttr, 'click', removeAttrFn);

    var parent = text.parentNode;
    wrapper.appendChild(text);
    wrapper.appendChild(removeAttr);
    parent.appendChild(wrapper);
  };

  var addTextArea = function (index, name, value) {
    names[index] = name;
    texts[index] = form.addTextarea(names[count] + ':', value, 2);
    texts[index].style.width = '100%';

    if (value.indexOf('\n') > 0) {
      texts[index].setAttribute('rows', '2');
    }

    addRemoveButton(texts[index], name);

    if (meta[name] != null && meta[name].editable == false) {
      texts[index].setAttribute('disabled', 'disabled');
    }
  };

  var temp = [];
  var isLayer = graph.getModel().getParent(cell) == graph.getModel().getRoot();

  for (var i = 0; i < attrs.length; i++) {
    if ((isLayer || attrs[i].nodeName != 'label') && attrs[i].nodeName != 'placeholders') {
      temp.push({ name: attrs[i].nodeName, value: attrs[i].nodeValue });
    }
  }

  // Sorts by name
  temp.sort(function (a, b) {
    if (a.name < b.name) {
      return -1;
    } else if (a.name > b.name) {
      return 1;
    } else {
      return 0;
    }
  });

  if (id != null) {
    var text = document.createElement('div');
    text.style.width = '100%';
    text.style.fontSize = '11px';
    text.style.textAlign = 'center';
    mxUtils.write(text, id);

    form.addField(mxResources.get('id') + ':', text);
  }

  for (var i = 0; i < temp.length; i++) {
    addTextArea(count, temp[i].name, temp[i].value);
    count++;
  }

  var top = document.createElement('div');
  top.style.cssText = 'position:absolute;left:30px;right:30px;overflow-y:auto;top:30px;bottom:80px;';
  top.appendChild(form.table);

  var newProp = document.createElement('div');
  newProp.style.boxSizing = 'border-box';
  newProp.style.paddingRight = '160px';
  newProp.style.whiteSpace = 'nowrap';
  newProp.style.marginTop = '6px';
  newProp.style.width = '100%';

  var nameInput = document.createElement('input');
  nameInput.setAttribute('placeholder', mxResources.get('enterPropertyName'));
  nameInput.setAttribute('type', 'text');
  nameInput.setAttribute('size', mxClient.IS_IE || mxClient.IS_IE11 ? '36' : '40');
  nameInput.style.boxSizing = 'border-box';
  nameInput.style.marginLeft = '2px';
  nameInput.style.width = '100%';

  newProp.appendChild(nameInput);
  top.appendChild(newProp);
  div.appendChild(top);

  var addBtn = mxUtils.button(mxResources.get('addProperty'), function () {
    var name = nameInput.value;

    // Avoid ':' in attribute names which seems to be valid in Chrome
    if (name.length > 0 && name != 'label' && name != 'placeholders' && name.indexOf(':') < 0) {
      try {
        var idx = mxUtils.indexOf(names, name);

        if (idx >= 0 && texts[idx] != null) {
          texts[idx].focus();
        } else {
          // Checks if the name is valid
          var clone = value.cloneNode(false);
          clone.setAttribute(name, '');

          if (idx >= 0) {
            names.splice(idx, 1);
            texts.splice(idx, 1);
          }

          names.push(name);
          var text = form.addTextarea(name + ':', '', 2);
          text.style.width = '100%';
          texts.push(text);
          addRemoveButton(text, name);

          text.focus();
        }

        addBtn.setAttribute('disabled', 'disabled');
        nameInput.value = '';
      } catch (e) {
        mxUtils.alert(e);
      }
    } else {
      mxUtils.alert(mxResources.get('invalidName'));
    }
  });

  this.init = function () {
    if (texts.length > 0) {
      texts[0].focus();
    } else {
      nameInput.focus();
    }
  };

  addBtn.setAttribute('title', mxResources.get('addProperty'));
  addBtn.setAttribute('disabled', 'disabled');
  addBtn.style.textOverflow = 'ellipsis';
  addBtn.style.position = 'absolute';
  addBtn.style.overflow = 'hidden';
  addBtn.style.width = '144px';
  addBtn.style.right = '0px';
  addBtn.className = 'geBtn';
  newProp.appendChild(addBtn);

  var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
    ui.hideDialog.apply(ui, arguments);
  });

  cancelBtn.className = 'geBtn';

  var applyBtn = mxUtils.button(mxResources.get('apply'), function () {
    try {
      ui.hideDialog.apply(ui, arguments);

      // Clones and updates the value
      value = value.cloneNode(true);
      var removeLabel = false;

      for (var i = 0; i < names.length; i++) {
        if (texts[i] == null) {
          value.removeAttribute(names[i]);
        } else {
          value.setAttribute(names[i], texts[i].value);
          removeLabel = removeLabel || (names[i] == 'placeholder' && value.getAttribute('placeholders') == '1');
        }
      }

      // Removes label if placeholder is assigned
      if (removeLabel) {
        value.removeAttribute('label');
      }

      // Updates the value of the cell (undoable)
      graph.getModel().setValue(cell, value);
    } catch (e) {
      mxUtils.alert(e);
    }
  });
  applyBtn.className = 'geBtn gePrimaryBtn';

  function updateAddBtn() {
    if (nameInput.value.length > 0) {
      addBtn.removeAttribute('disabled');
    } else {
      addBtn.setAttribute('disabled', 'disabled');
    }
  }

  mxEvent.addListener(nameInput, 'keyup', updateAddBtn);

  // Catches all changes that don't fire a keyup (such as paste via mouse)
  mxEvent.addListener(nameInput, 'change', updateAddBtn);

  var buttons = document.createElement('div');
  buttons.style.cssText = 'position:absolute;left:30px;right:30px;text-align:right;bottom:30px;height:40px;';

  if (ui.editor.graph.getModel().isVertex(cell) || ui.editor.graph.getModel().isEdge(cell)) {
    var replace = document.createElement('span');
    replace.style.marginRight = '10px';
    var input = document.createElement('input');
    input.setAttribute('type', 'checkbox');
    input.style.marginRight = '6px';

    if (value.getAttribute('placeholders') == '1') {
      input.setAttribute('checked', 'checked');
      input.defaultChecked = true;
    }

    mxEvent.addListener(input, 'click', function () {
      if (value.getAttribute('placeholders') == '1') {
        value.removeAttribute('placeholders');
      } else {
        value.setAttribute('placeholders', '1');
      }
    });

    replace.appendChild(input);
    mxUtils.write(replace, mxResources.get('placeholders'));

    if (EditDataDialog.placeholderHelpLink != null) {
      var link = document.createElement('a');
      link.setAttribute('href', EditDataDialog.placeholderHelpLink);
      link.setAttribute('title', mxResources.get('help'));
      link.setAttribute('target', '_blank');
      link.style.marginLeft = '8px';
      link.style.cursor = 'help';

      var icon = document.createElement('img');
      mxUtils.setOpacity(icon, 50);
      icon.style.height = '16px';
      icon.style.width = '16px';
      icon.setAttribute('border', '0');
      icon.setAttribute('valign', 'middle');
      icon.style.marginTop = mxClient.IS_IE11 ? '0px' : '-4px';
      icon.setAttribute('src', Editor.helpImage);
      link.appendChild(icon);

      replace.appendChild(link);
    }

    buttons.appendChild(replace);
  }

  if (ui.editor.cancelFirst) {
    buttons.appendChild(cancelBtn);
    buttons.appendChild(applyBtn);
  } else {
    buttons.appendChild(applyBtn);
    buttons.appendChild(cancelBtn);
  }

  div.appendChild(buttons);
  this.container = div;
};

/**
 * Optional help link.
 */
EditDataDialog.getDisplayIdForCell = function (ui, cell) {
  var id = null;

  if (ui.editor.graph.getModel().getParent(cell) != null) {
    id = cell.getId();
  }

  return id;
};

/**
 * Optional help link.
 */
EditDataDialog.placeholderHelpLink = null;

/**
 * Constructs a new link dialog.
 */
var LinkDialog = function (editorUi, initialValue, btnLabel, fn) {
  var div = document.createElement('div');
  mxUtils.write(div, mxResources.get('editLink') + ':');

  var inner = document.createElement('div');
  inner.className = 'geTitle';
  inner.style.backgroundColor = 'transparent';
  inner.style.borderColor = 'transparent';
  inner.style.whiteSpace = 'nowrap';
  inner.style.textOverflow = 'clip';
  inner.style.cursor = 'default';

  if (!mxClient.IS_VML) {
    inner.style.paddingRight = '20px';
  }

  var linkInput = document.createElement('input');
  linkInput.setAttribute('value', initialValue);
  linkInput.setAttribute('placeholder', 'http://www.example.com/');
  linkInput.setAttribute('type', 'text');
  linkInput.style.marginTop = '6px';
  linkInput.style.width = '400px';
  linkInput.style.backgroundImage = "url('" + Dialog.prototype.clearImage + "')";
  linkInput.style.backgroundRepeat = 'no-repeat';
  linkInput.style.backgroundPosition = '100% 50%';
  linkInput.style.paddingRight = '14px';

  var cross = document.createElement('div');
  cross.setAttribute('title', mxResources.get('reset'));
  cross.style.position = 'relative';
  cross.style.left = '-16px';
  cross.style.width = '12px';
  cross.style.height = '14px';
  cross.style.cursor = 'pointer';

  // Workaround for inline-block not supported in IE
  cross.style.display = mxClient.IS_VML ? 'inline' : 'inline-block';
  cross.style.top = (mxClient.IS_VML ? 0 : 3) + 'px';

  // Needed to block event transparency in IE
  cross.style.background = 'url(' + IMAGE_PATH + '/transparent.gif)';

  mxEvent.addListener(cross, 'click', function () {
    linkInput.value = '';
    linkInput.focus();
  });

  inner.appendChild(linkInput);
  inner.appendChild(cross);
  div.appendChild(inner);

  this.init = function () {
    linkInput.focus();

    if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5 || mxClient.IS_QUIRKS) {
      linkInput.select();
    } else {
      document.execCommand('selectAll', false, null);
    }
  };

  var btns = document.createElement('div');
  btns.style.marginTop = '18px';
  btns.style.textAlign = 'right';

  mxEvent.addListener(linkInput, 'keypress', function (e) {
    if (e.keyCode == 13) {
      editorUi.hideDialog();
      fn(linkInput.value);
    }
  });

  var cancelBtn = mxUtils.button(mxResources.get('cancel'), function () {
    editorUi.hideDialog();
  });
  cancelBtn.className = 'geBtn';

  if (editorUi.editor.cancelFirst) {
    btns.appendChild(cancelBtn);
  }

  var mainBtn = mxUtils.button(btnLabel, function () {
    editorUi.hideDialog();
    fn(linkInput.value);
  });
  mainBtn.className = 'geBtn gePrimaryBtn';
  btns.appendChild(mainBtn);

  if (!editorUi.editor.cancelFirst) {
    btns.appendChild(cancelBtn);
  }

  div.appendChild(btns);

  this.container = div;
};

/**
 *
 */
var OutlineWindow = function (editorUi, x, y, w, h) {
  var graph = editorUi.editor.graph;

  var div = document.createElement('div');
  div.style.position = 'absolute';
  div.style.width = '100%';
  div.style.height = '100%';
  div.style.border = '1px solid whiteSmoke';
  div.style.overflow = 'hidden';

  this.window = new mxWindow(mxResources.get('outline'), div, x, y, w, h, true, true);
  this.window.minimumSize = new mxRectangle(0, 0, 80, 80);
  this.window.destroyOnClose = false;
  this.window.setMaximizable(false);
  this.window.setResizable(true);
  this.window.setClosable(true);
  this.window.setVisible(true);

  this.window.setLocation = function (x, y) {
    var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
    var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;

    x = Math.max(0, Math.min(x, iw - this.table.clientWidth));
    y = Math.max(0, Math.min(y, ih - this.table.clientHeight - 48));

    if (this.getX() != x || this.getY() != y) {
      mxWindow.prototype.setLocation.apply(this, arguments);
    }
  };

  var resizeListener = mxUtils.bind(this, function () {
    var x = this.window.getX();
    var y = this.window.getY();

    this.window.setLocation(x, y);
  });

  mxEvent.addListener(window, 'resize', resizeListener);

  var outline = editorUi.createOutline(this.window);

  this.destroy = function () {
    mxEvent.removeListener(window, 'resize', resizeListener);
    this.window.destroy();
    outline.destroy();
  };

  this.window.addListener(
    mxEvent.RESIZE,
    mxUtils.bind(this, function () {
      outline.update(false);
      outline.outline.sizeDidChange();
    })
  );

  this.window.addListener(
    mxEvent.SHOW,
    mxUtils.bind(this, function () {
      this.window.fit();
      outline.suspended = false;
      outline.outline.refresh();
      outline.update();
    })
  );

  this.window.addListener(
    mxEvent.HIDE,
    mxUtils.bind(this, function () {
      outline.suspended = true;
    })
  );

  this.window.addListener(
    mxEvent.NORMALIZE,
    mxUtils.bind(this, function () {
      outline.suspended = false;
      outline.update();
    })
  );

  this.window.addListener(
    mxEvent.MINIMIZE,
    mxUtils.bind(this, function () {
      outline.suspended = true;
    })
  );

  var outlineCreateGraph = outline.createGraph;
  outline.createGraph = function (container) {
    var g = outlineCreateGraph.apply(this, arguments);
    g.gridEnabled = false;
    g.pageScale = graph.pageScale;
    g.pageFormat = graph.pageFormat;
    g.background =
      graph.background == null || graph.background == mxConstants.NONE
        ? graph.defaultPageBackgroundColor
        : graph.background;
    g.pageVisible = graph.pageVisible;

    var current = mxUtils.getCurrentStyle(graph.container);
    div.style.backgroundColor = current.backgroundColor;

    return g;
  };

  function update() {
    outline.outline.pageScale = graph.pageScale;
    outline.outline.pageFormat = graph.pageFormat;
    outline.outline.pageVisible = graph.pageVisible;
    outline.outline.background =
      graph.background == null || graph.background == mxConstants.NONE
        ? graph.defaultPageBackgroundColor
        : graph.background;

    var current = mxUtils.getCurrentStyle(graph.container);
    div.style.backgroundColor = current.backgroundColor;

    if (graph.view.backgroundPageShape != null && outline.outline.view.backgroundPageShape != null) {
      outline.outline.view.backgroundPageShape.fill = graph.view.backgroundPageShape.fill;
    }

    outline.outline.refresh();
  }

  outline.init(div);

  editorUi.editor.addListener('resetGraphView', update);
  editorUi.addListener('pageFormatChanged', update);
  editorUi.addListener('backgroundColorChanged', update);
  editorUi.addListener('backgroundImageChanged', update);
  editorUi.addListener('pageViewChanged', function () {
    update();
    outline.update(true);
  });

  if (outline.outline.dialect == mxConstants.DIALECT_SVG) {
    var zoomInAction = editorUi.actions.get('zoomIn');
    var zoomOutAction = editorUi.actions.get('zoomOut');

    mxEvent.addMouseWheelListener(function (evt, up) {
      var outlineWheel = false;
      var source = mxEvent.getSource(evt);

      while (source != null) {
        if (source == outline.outline.view.canvas.ownerSVGElement) {
          outlineWheel = true;
          break;
        }

        source = source.parentNode;
      }

      if (outlineWheel) {
        if (up) {
          zoomInAction.funct();
        } else {
          zoomOutAction.funct();
        }
      }
    });
  }
};

/**
 *
 */
var LayersWindow = function (editorUi, x, y, w, h) {
  var graph = editorUi.editor.graph;

  var div = document.createElement('div');
  div.style.userSelect = 'none';
  div.style.background = Dialog.backdropColor == 'white' ? 'whiteSmoke' : Dialog.backdropColor;
  div.style.border = '1px solid whiteSmoke';
  div.style.height = '100%';
  div.style.marginBottom = '10px';
  div.style.overflow = 'auto';

  var tbarHeight = !EditorUi.compactUi ? '30px' : '26px';

  var listDiv = document.createElement('div');
  listDiv.style.backgroundColor = Dialog.backdropColor == 'white' ? '#dcdcdc' : Dialog.backdropColor;
  listDiv.style.position = 'absolute';
  listDiv.style.overflow = 'auto';
  listDiv.style.left = '0px';
  listDiv.style.right = '0px';
  listDiv.style.top = '0px';
  listDiv.style.bottom = parseInt(tbarHeight) + 7 + 'px';
  div.appendChild(listDiv);

  var dragSource = null;
  var dropIndex = null;

  mxEvent.addListener(div, 'dragover', function (evt) {
    evt.dataTransfer.dropEffect = 'move';
    dropIndex = 0;
    evt.stopPropagation();
    evt.preventDefault();
  });

  // Workaround for "no element found" error in FF
  mxEvent.addListener(div, 'drop', function (evt) {
    evt.stopPropagation();
    evt.preventDefault();
  });

  var layerCount = null;
  var selectionLayer = null;
  var ldiv = document.createElement('div');

  ldiv.className = 'geToolbarContainer';
  ldiv.style.position = 'absolute';
  ldiv.style.bottom = '0px';
  ldiv.style.left = '0px';
  ldiv.style.right = '0px';
  ldiv.style.height = tbarHeight;
  ldiv.style.overflow = 'hidden';
  ldiv.style.padding = !EditorUi.compactUi ? '1px' : '4px 0px 3px 0px';
  ldiv.style.backgroundColor = Dialog.backdropColor == 'white' ? 'whiteSmoke' : Dialog.backdropColor;
  ldiv.style.borderWidth = '1px 0px 0px 0px';
  ldiv.style.borderColor = '#c3c3c3';
  ldiv.style.borderStyle = 'solid';
  ldiv.style.display = 'block';
  ldiv.style.whiteSpace = 'nowrap';

  if (mxClient.IS_QUIRKS) {
    ldiv.style.filter = 'none';
  }

  var link = document.createElement('a');
  link.className = 'geButton';

  if (mxClient.IS_QUIRKS) {
    link.style.filter = 'none';
  }

  var removeLink = link.cloneNode();
  removeLink.innerHTML = '<div class="geSprite geSprite-delete" style="display:inline-block;"></div>';

  mxEvent.addListener(removeLink, 'click', function (evt) {
    if (graph.isEnabled()) {
      graph.model.beginUpdate();
      try {
        var index = graph.model.root.getIndex(selectionLayer);
        graph.removeCells([selectionLayer], false);

        // Creates default layer if no layer exists
        if (graph.model.getChildCount(graph.model.root) == 0) {
          graph.model.add(graph.model.root, new mxCell());
          graph.setDefaultParent(null);
        } else if (index > 0 && index <= graph.model.getChildCount(graph.model.root)) {
          graph.setDefaultParent(graph.model.getChildAt(graph.model.root, index - 1));
        } else {
          graph.setDefaultParent(null);
        }
      } finally {
        graph.model.endUpdate();
      }
    }

    mxEvent.consume(evt);
  });

  if (!graph.isEnabled()) {
    removeLink.className = 'geButton mxDisabled';
  }

  ldiv.appendChild(removeLink);

  var insertLink = link.cloneNode();
  insertLink.setAttribute('title', mxUtils.trim(mxResources.get('moveSelectionTo', ['...'])));
  insertLink.innerHTML = '<div class="geSprite geSprite-insert" style="display:inline-block;"></div>';

  mxEvent.addListener(insertLink, 'click', function (evt) {
    if (graph.isEnabled() && !graph.isSelectionEmpty()) {
      var offset = mxUtils.getOffset(insertLink);

      editorUi.showPopupMenu(
        mxUtils.bind(this, function (menu, parent) {
          for (var i = layerCount - 1; i >= 0; i--) {
            mxUtils.bind(this, function (child) {
              var item = menu.addItem(
                graph.convertValueToString(child) || mxResources.get('background'),
                null,
                mxUtils.bind(this, function () {
                  graph.moveCells(graph.getSelectionCells(), 0, 0, false, child);
                }),
                parent
              );

              if (graph.getSelectionCount() == 1 && graph.model.isAncestor(child, graph.getSelectionCell())) {
                menu.addCheckmark(item, Editor.checkmarkImage);
              }
            })(graph.model.getChildAt(graph.model.root, i));
          }
        }),
        offset.x,
        offset.y + insertLink.offsetHeight,
        evt
      );
    }
  });

  ldiv.appendChild(insertLink);

  var dataLink = link.cloneNode();
  dataLink.innerHTML = '<div class="geSprite geSprite-dots" style="display:inline-block;"></div>';
  dataLink.setAttribute('title', mxResources.get('rename'));

  mxEvent.addListener(dataLink, 'click', function (evt) {
    if (graph.isEnabled()) {
      editorUi.showDataDialog(selectionLayer);
    }

    mxEvent.consume(evt);
  });

  if (!graph.isEnabled()) {
    dataLink.className = 'geButton mxDisabled';
  }

  ldiv.appendChild(dataLink);

  function renameLayer(layer) {
    if (graph.isEnabled() && layer != null) {
      var label = graph.convertValueToString(layer);
      var dlg = new FilenameDialog(
        editorUi,
        label || mxResources.get('background'),
        mxResources.get('rename'),
        mxUtils.bind(this, function (newValue) {
          if (newValue != null) {
            graph.cellLabelChanged(layer, newValue);
          }
        }),
        mxResources.get('enterName')
      );
      editorUi.showDialog(dlg.container, 300, 100, true, true);
      dlg.init();
    }
  }

  var duplicateLink = link.cloneNode();
  duplicateLink.innerHTML = '<div class="geSprite geSprite-duplicate" style="display:inline-block;"></div>';

  mxEvent.addListener(duplicateLink, 'click', function (evt) {
    if (graph.isEnabled()) {
      var newCell = null;
      graph.model.beginUpdate();
      try {
        newCell = graph.cloneCell(selectionLayer);
        graph.cellLabelChanged(newCell, mxResources.get('untitledLayer'));
        newCell.setVisible(true);
        newCell = graph.addCell(newCell, graph.model.root);
        graph.setDefaultParent(newCell);
      } finally {
        graph.model.endUpdate();
      }

      if (newCell != null && !graph.isCellLocked(newCell)) {
        graph.selectAll(newCell);
      }
    }
  });

  if (!graph.isEnabled()) {
    duplicateLink.className = 'geButton mxDisabled';
  }

  ldiv.appendChild(duplicateLink);

  var addLink = link.cloneNode();
  addLink.innerHTML = '<div class="geSprite geSprite-plus" style="display:inline-block;"></div>';
  addLink.setAttribute('title', mxResources.get('addLayer'));

  mxEvent.addListener(addLink, 'click', function (evt) {
    if (graph.isEnabled()) {
      graph.model.beginUpdate();

      try {
        var cell = graph.addCell(new mxCell(mxResources.get('untitledLayer')), graph.model.root);
        graph.setDefaultParent(cell);
      } finally {
        graph.model.endUpdate();
      }
    }

    mxEvent.consume(evt);
  });

  if (!graph.isEnabled()) {
    addLink.className = 'geButton mxDisabled';
  }

  ldiv.appendChild(addLink);
  div.appendChild(ldiv);

  function refresh() {
    layerCount = graph.model.getChildCount(graph.model.root);
    listDiv.innerHTML = '';

    function addLayer(index, label, child, defaultParent) {
      var ldiv = document.createElement('div');
      ldiv.className = 'geToolbarContainer';

      ldiv.style.overflow = 'hidden';
      ldiv.style.position = 'relative';
      ldiv.style.padding = '4px';
      ldiv.style.height = '22px';
      ldiv.style.display = 'block';
      ldiv.style.backgroundColor = Dialog.backdropColor == 'white' ? 'whiteSmoke' : Dialog.backdropColor;
      ldiv.style.borderWidth = '0px 0px 1px 0px';
      ldiv.style.borderColor = '#c3c3c3';
      ldiv.style.borderStyle = 'solid';
      ldiv.style.whiteSpace = 'nowrap';
      ldiv.setAttribute('title', label);

      var left = document.createElement('div');
      left.style.display = 'inline-block';
      left.style.width = '100%';
      left.style.textOverflow = 'ellipsis';
      left.style.overflow = 'hidden';

      mxEvent.addListener(ldiv, 'dragover', function (evt) {
        evt.dataTransfer.dropEffect = 'move';
        dropIndex = index;
        evt.stopPropagation();
        evt.preventDefault();
      });

      mxEvent.addListener(ldiv, 'dragstart', function (evt) {
        dragSource = ldiv;

        // Workaround for no DnD on DIV in FF
        if (mxClient.IS_FF) {
          // LATER: Check what triggers a parse as XML on this in FF after drop
          evt.dataTransfer.setData('Text', '<layer/>');
        }
      });

      mxEvent.addListener(ldiv, 'dragend', function (evt) {
        if (dragSource != null && dropIndex != null) {
          graph.addCell(child, graph.model.root, dropIndex);
        }

        dragSource = null;
        dropIndex = null;
        evt.stopPropagation();
        evt.preventDefault();
      });

      var btn = document.createElement('img');
      btn.setAttribute('draggable', 'false');
      btn.setAttribute('align', 'top');
      btn.setAttribute('border', '0');
      btn.style.padding = '4px';
      btn.setAttribute('title', mxResources.get('lockUnlock'));

      var style = graph.getCurrentCellStyle(child);

      if (mxUtils.getValue(style, 'locked', '0') == '1') {
        btn.setAttribute('src', Dialog.prototype.lockedImage);
      } else {
        btn.setAttribute('src', Dialog.prototype.unlockedImage);
      }

      if (graph.isEnabled()) {
        btn.style.cursor = 'pointer';
      }

      mxEvent.addListener(btn, 'click', function (evt) {
        if (graph.isEnabled()) {
          var value = null;

          graph.getModel().beginUpdate();
          try {
            value = mxUtils.getValue(style, 'locked', '0') == '1' ? null : '1';
            graph.setCellStyles('locked', value, [child]);
          } finally {
            graph.getModel().endUpdate();
          }

          if (value == '1') {
            graph.removeSelectionCells(graph.getModel().getDescendants(child));
          }

          mxEvent.consume(evt);
        }
      });

      left.appendChild(btn);

      var inp = document.createElement('input');
      inp.setAttribute('type', 'checkbox');
      inp.setAttribute('title', mxResources.get('hideIt', [child.value || mxResources.get('background')]));
      inp.style.marginLeft = '4px';
      inp.style.marginRight = '6px';
      inp.style.marginTop = '4px';
      left.appendChild(inp);

      if (graph.model.isVisible(child)) {
        inp.setAttribute('checked', 'checked');
        inp.defaultChecked = true;
      }

      mxEvent.addListener(inp, 'click', function (evt) {
        graph.model.setVisible(child, !graph.model.isVisible(child));
        mxEvent.consume(evt);
      });

      mxUtils.write(left, label);
      ldiv.appendChild(left);

      if (graph.isEnabled()) {
        // Fallback if no drag and drop is available
        if (
          mxClient.IS_TOUCH ||
          mxClient.IS_POINTER ||
          mxClient.IS_VML ||
          (mxClient.IS_IE && document.documentMode < 10)
        ) {
          var right = document.createElement('div');
          right.style.display = 'block';
          right.style.textAlign = 'right';
          right.style.whiteSpace = 'nowrap';
          right.style.position = 'absolute';
          right.style.right = '6px';
          right.style.top = '6px';

          // Poor man's change layer order
          if (index > 0) {
            var img2 = document.createElement('a');

            img2.setAttribute('title', mxResources.get('toBack'));

            img2.className = 'geButton';
            img2.style.cssFloat = 'none';
            img2.innerHTML = '&#9660;';
            img2.style.width = '14px';
            img2.style.height = '14px';
            img2.style.fontSize = '14px';
            img2.style.margin = '0px';
            img2.style.marginTop = '-1px';
            right.appendChild(img2);

            mxEvent.addListener(img2, 'click', function (evt) {
              if (graph.isEnabled()) {
                graph.addCell(child, graph.model.root, index - 1);
              }

              mxEvent.consume(evt);
            });
          }

          if (index >= 0 && index < layerCount - 1) {
            var img1 = document.createElement('a');

            img1.setAttribute('title', mxResources.get('toFront'));

            img1.className = 'geButton';
            img1.style.cssFloat = 'none';
            img1.innerHTML = '&#9650;';
            img1.style.width = '14px';
            img1.style.height = '14px';
            img1.style.fontSize = '14px';
            img1.style.margin = '0px';
            img1.style.marginTop = '-1px';
            right.appendChild(img1);

            mxEvent.addListener(img1, 'click', function (evt) {
              if (graph.isEnabled()) {
                graph.addCell(child, graph.model.root, index + 1);
              }

              mxEvent.consume(evt);
            });
          }

          ldiv.appendChild(right);
        }

        if (mxClient.IS_SVG && (!mxClient.IS_IE || document.documentMode >= 10)) {
          ldiv.setAttribute('draggable', 'true');
          ldiv.style.cursor = 'move';
        }
      }

      mxEvent.addListener(ldiv, 'dblclick', function (evt) {
        var nodeName = mxEvent.getSource(evt).nodeName;

        if (nodeName != 'INPUT' && nodeName != 'IMG') {
          renameLayer(child);
          mxEvent.consume(evt);
        }
      });

      if (graph.getDefaultParent() == child) {
        ldiv.style.background = Dialog.backdropColor == 'white' ? '#e6eff8' : '#505759';
        ldiv.style.fontWeight = graph.isEnabled() ? 'bold' : '';
        selectionLayer = child;
      } else {
        mxEvent.addListener(ldiv, 'click', function (evt) {
          if (graph.isEnabled()) {
            graph.setDefaultParent(defaultParent);
            graph.view.setCurrentRoot(null);
          }
        });
      }

      listDiv.appendChild(ldiv);
    }

    // Cannot be moved or deleted
    for (var i = layerCount - 1; i >= 0; i--) {
      mxUtils.bind(this, function (child) {
        addLayer(i, graph.convertValueToString(child) || mxResources.get('background'), child, child);
      })(graph.model.getChildAt(graph.model.root, i));
    }

    var label = graph.convertValueToString(selectionLayer) || mxResources.get('background');
    removeLink.setAttribute('title', mxResources.get('removeIt', [label]));
    duplicateLink.setAttribute('title', mxResources.get('duplicateIt', [label]));
    dataLink.setAttribute('title', mxResources.get('editData'));

    if (graph.isSelectionEmpty()) {
      insertLink.className = 'geButton mxDisabled';
    }
  }

  refresh();
  graph.model.addListener(mxEvent.CHANGE, refresh);
  graph.addListener('defaultParentChanged', refresh);

  graph.selectionModel.addListener(mxEvent.CHANGE, function () {
    if (graph.isSelectionEmpty()) {
      insertLink.className = 'geButton mxDisabled';
    } else {
      insertLink.className = 'geButton';
    }
  });

  this.window = new mxWindow(mxResources.get('layers'), div, x, y, w, h, true, true);
  this.window.minimumSize = new mxRectangle(0, 0, 120, 120);
  this.window.destroyOnClose = false;
  this.window.setMaximizable(false);
  this.window.setResizable(true);
  this.window.setClosable(true);
  this.window.setVisible(true);

  this.init = function () {
    listDiv.scrollTop = listDiv.scrollHeight - listDiv.clientHeight;
  };

  this.window.addListener(
    mxEvent.SHOW,
    mxUtils.bind(this, function () {
      this.window.fit();
    })
  );

  // Make refresh available via instance
  this.refreshLayers = refresh;

  this.window.setLocation = function (x, y) {
    var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
    var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;

    x = Math.max(0, Math.min(x, iw - this.table.clientWidth));
    y = Math.max(0, Math.min(y, ih - this.table.clientHeight - 48));

    if (this.getX() != x || this.getY() != y) {
      mxWindow.prototype.setLocation.apply(this, arguments);
    }
  };

  var resizeListener = mxUtils.bind(this, function () {
    var x = this.window.getX();
    var y = this.window.getY();

    this.window.setLocation(x, y);
  });

  mxEvent.addListener(window, 'resize', resizeListener);

  this.destroy = function () {
    mxEvent.removeListener(window, 'resize', resizeListener);
    this.window.destroy();
  };
};

/** eslint-disable */
// S 草图支持
function rotatePoints(points, center, degrees) {
  if (points && points.length) {
    const [cx, cy] = center;
    const angle = (Math.PI / 180) * degrees;
    const cos = Math.cos(angle);
    const sin = Math.sin(angle);
    points.forEach((p) => {
      const [x, y] = p;
      p[0] = (x - cx) * cos - (y - cy) * sin + cx;
      p[1] = (x - cx) * sin + (y - cy) * cos + cy;
    });
  }
}
function rotateLines(lines, center, degrees) {
  const points = [];
  lines.forEach((line) => points.push(...line));
  rotatePoints(points, center, degrees);
}
function lineLength(line) {
  const p1 = line[0];
  const p2 = line[1];
  return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
}

function polygonHachureLines(polygonList, o) {
  const angle = o.hachureAngle + 90;
  let gap = o.hachureGap;
  if (gap < 0) {
    gap = o.strokeWidth * 4;
  }
  gap = Math.max(gap, 0.1);
  const rotationCenter = [0, 0];
  if (angle) {
    for (const polygon of polygonList) {
      rotatePoints(polygon, rotationCenter, angle);
    }
  }
  const lines = straightHachureLines(polygonList, gap);
  if (angle) {
    for (const polygon of polygonList) {
      rotatePoints(polygon, rotationCenter, -angle);
    }
    rotateLines(lines, rotationCenter, -angle);
  }
  return lines;
}
function straightHachureLines(polygonList, gap) {
  const vertexArray = [];
  for (const polygon of polygonList) {
    const vertices = [...polygon];
    if (vertices[0].join(',') !== vertices[vertices.length - 1].join(',')) {
      vertices.push([vertices[0][0], vertices[0][1]]);
    }
    if (vertices.length > 2) {
      vertexArray.push(vertices);
    }
  }
  const lines = [];
  gap = Math.max(gap, 0.1);
  // Create sorted edges table
  const edges = [];
  for (const vertices of vertexArray) {
    for (let i = 0; i < vertices.length - 1; i++) {
      const p1 = vertices[i];
      const p2 = vertices[i + 1];
      if (p1[1] !== p2[1]) {
        const ymin = Math.min(p1[1], p2[1]);
        edges.push({
          ymin,
          ymax: Math.max(p1[1], p2[1]),
          x: ymin === p1[1] ? p1[0] : p2[0],
          islope: (p2[0] - p1[0]) / (p2[1] - p1[1]),
        });
      }
    }
  }
  edges.sort((e1, e2) => {
    if (e1.ymin < e2.ymin) {
      return -1;
    }
    if (e1.ymin > e2.ymin) {
      return 1;
    }
    if (e1.x < e2.x) {
      return -1;
    }
    if (e1.x > e2.x) {
      return 1;
    }
    if (e1.ymax === e2.ymax) {
      return 0;
    }
    return (e1.ymax - e2.ymax) / Math.abs(e1.ymax - e2.ymax);
  });
  if (!edges.length) {
    return lines;
  }
  // Start scanning
  let activeEdges = [];
  let y = edges[0].ymin;
  while (activeEdges.length || edges.length) {
    if (edges.length) {
      let ix = -1;
      for (let i = 0; i < edges.length; i++) {
        if (edges[i].ymin > y) {
          break;
        }
        ix = i;
      }
      const removed = edges.splice(0, ix + 1);
      removed.forEach((edge) => {
        activeEdges.push({ s: y, edge });
      });
    }
    activeEdges = activeEdges.filter((ae) => {
      if (ae.edge.ymax <= y) {
        return false;
      }
      return true;
    });
    activeEdges.sort((ae1, ae2) => {
      if (ae1.edge.x === ae2.edge.x) {
        return 0;
      }
      return (ae1.edge.x - ae2.edge.x) / Math.abs(ae1.edge.x - ae2.edge.x);
    });
    // fill between the edges
    if (activeEdges.length > 1) {
      for (let i = 0; i < activeEdges.length; i = i + 2) {
        const nexti = i + 1;
        if (nexti >= activeEdges.length) {
          break;
        }
        const ce = activeEdges[i].edge;
        const ne = activeEdges[nexti].edge;
        lines.push([
          [Math.round(ce.x), y],
          [Math.round(ne.x), y],
        ]);
      }
    }
    y += gap;
    activeEdges.forEach((ae) => {
      ae.edge.x = ae.edge.x + gap * ae.edge.islope;
    });
  }
  return lines;
}

class HachureFiller {
  constructor(helper) {
    this.helper = helper;
  }
  fillPolygons(polygonList, o) {
    return this._fillPolygons(polygonList, o);
  }
  _fillPolygons(polygonList, o) {
    const lines = polygonHachureLines(polygonList, o);
    const ops = this.renderLines(lines, o);
    return { type: 'fillSketch', ops };
  }
  renderLines(lines, o) {
    const ops = [];
    for (const line of lines) {
      ops.push(...this.helper.doubleLineOps(line[0][0], line[0][1], line[1][0], line[1][1], o));
    }
    return ops;
  }
}

class ZigZagFiller extends HachureFiller {
  fillPolygons(polygonList, o) {
    let gap = o.hachureGap;
    if (gap < 0) {
      gap = o.strokeWidth * 4;
    }
    gap = Math.max(gap, 0.1);
    const o2 = Object.assign({}, o, { hachureGap: gap });
    const lines = polygonHachureLines(polygonList, o2);
    const zigZagAngle = (Math.PI / 180) * o.hachureAngle;
    const zigzagLines = [];
    const dgx = gap * 0.5 * Math.cos(zigZagAngle);
    const dgy = gap * 0.5 * Math.sin(zigZagAngle);
    for (const [p1, p2] of lines) {
      if (lineLength([p1, p2])) {
        zigzagLines.push([[p1[0] - dgx, p1[1] + dgy], [...p2]], [[p1[0] + dgx, p1[1] - dgy], [...p2]]);
      }
    }
    const ops = this.renderLines(zigzagLines, o);
    return { type: 'fillSketch', ops };
  }
}

class HatchFiller extends HachureFiller {
  fillPolygons(polygonList, o) {
    const set = this._fillPolygons(polygonList, o);
    const o2 = Object.assign({}, o, { hachureAngle: o.hachureAngle + 90 });
    const set2 = this._fillPolygons(polygonList, o2);
    set.ops = set.ops.concat(set2.ops);
    return set;
  }
}

class DotFiller {
  constructor(helper) {
    this.helper = helper;
  }
  fillPolygons(polygonList, o) {
    o = Object.assign({}, o, { hachureAngle: 0 });
    const lines = polygonHachureLines(polygonList, o);
    return this.dotsOnLines(lines, o);
  }
  dotsOnLines(lines, o) {
    const ops = [];
    let gap = o.hachureGap;
    if (gap < 0) {
      gap = o.strokeWidth * 4;
    }
    gap = Math.max(gap, 0.1);
    let fweight = o.fillWeight;
    if (fweight < 0) {
      fweight = o.strokeWidth / 2;
    }
    const ro = gap / 4;
    for (const line of lines) {
      const length = lineLength(line);
      const dl = length / gap;
      const count = Math.ceil(dl) - 1;
      const offset = length - count * gap;
      const x = (line[0][0] + line[1][0]) / 2 - gap / 4;
      const minY = Math.min(line[0][1], line[1][1]);
      for (let i = 0; i < count; i++) {
        const y = minY + offset + i * gap;
        const cx = x - ro + Math.random() * 2 * ro;
        const cy = y - ro + Math.random() * 2 * ro;
        const el = this.helper.ellipse(cx, cy, fweight, fweight, o);
        ops.push(...el.ops);
      }
    }
    return { type: 'fillSketch', ops };
  }
}

class DashedFiller {
  constructor(helper) {
    this.helper = helper;
  }
  fillPolygons(polygonList, o) {
    const lines = polygonHachureLines(polygonList, o);
    return { type: 'fillSketch', ops: this.dashedLine(lines, o) };
  }
  dashedLine(lines, o) {
    const offset = o.dashOffset < 0 ? (o.hachureGap < 0 ? o.strokeWidth * 4 : o.hachureGap) : o.dashOffset;
    const gap = o.dashGap < 0 ? (o.hachureGap < 0 ? o.strokeWidth * 4 : o.hachureGap) : o.dashGap;
    const ops = [];
    lines.forEach((line) => {
      const length = lineLength(line);
      const count = Math.floor(length / (offset + gap));
      const startOffset = (length + gap - count * (offset + gap)) / 2;
      let p1 = line[0];
      let p2 = line[1];
      if (p1[0] > p2[0]) {
        p1 = line[1];
        p2 = line[0];
      }
      const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
      for (let i = 0; i < count; i++) {
        const lstart = i * (offset + gap);
        const lend = lstart + offset;
        const start = [
          p1[0] + lstart * Math.cos(alpha) + startOffset * Math.cos(alpha),
          p1[1] + lstart * Math.sin(alpha) + startOffset * Math.sin(alpha),
        ];
        const end = [
          p1[0] + lend * Math.cos(alpha) + startOffset * Math.cos(alpha),
          p1[1] + lend * Math.sin(alpha) + startOffset * Math.sin(alpha),
        ];
        ops.push(...this.helper.doubleLineOps(start[0], start[1], end[0], end[1], o));
      }
    });
    return ops;
  }
}

class ZigZagLineFiller {
  constructor(helper) {
    this.helper = helper;
  }
  fillPolygons(polygonList, o) {
    const gap = o.hachureGap < 0 ? o.strokeWidth * 4 : o.hachureGap;
    const zo = o.zigzagOffset < 0 ? gap : o.zigzagOffset;
    o = Object.assign({}, o, { hachureGap: gap + zo });
    const lines = polygonHachureLines(polygonList, o);
    return { type: 'fillSketch', ops: this.zigzagLines(lines, zo, o) };
  }
  zigzagLines(lines, zo, o) {
    const ops = [];
    lines.forEach((line) => {
      const length = lineLength(line);
      const count = Math.round(length / (2 * zo));
      let p1 = line[0];
      let p2 = line[1];
      if (p1[0] > p2[0]) {
        p1 = line[1];
        p2 = line[0];
      }
      const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
      for (let i = 0; i < count; i++) {
        const lstart = i * 2 * zo;
        const lend = (i + 1) * 2 * zo;
        const dz = Math.sqrt(2 * Math.pow(zo, 2));
        const start = [p1[0] + lstart * Math.cos(alpha), p1[1] + lstart * Math.sin(alpha)];
        const end = [p1[0] + lend * Math.cos(alpha), p1[1] + lend * Math.sin(alpha)];
        const middle = [start[0] + dz * Math.cos(alpha + Math.PI / 4), start[1] + dz * Math.sin(alpha + Math.PI / 4)];
        ops.push(
          ...this.helper.doubleLineOps(start[0], start[1], middle[0], middle[1], o),
          ...this.helper.doubleLineOps(middle[0], middle[1], end[0], end[1], o)
        );
      }
    });
    return ops;
  }
}

const fillers = {};
function getFiller(o, helper) {
  let fillerName = o.fillStyle || 'hachure';
  if (!fillers[fillerName]) {
    switch (fillerName) {
      case 'zigzag':
        if (!fillers[fillerName]) {
          fillers[fillerName] = new ZigZagFiller(helper);
        }
        break;
      case 'cross-hatch':
        if (!fillers[fillerName]) {
          fillers[fillerName] = new HatchFiller(helper);
        }
        break;
      case 'dots':
        if (!fillers[fillerName]) {
          fillers[fillerName] = new DotFiller(helper);
        }
        break;
      case 'dashed':
        if (!fillers[fillerName]) {
          fillers[fillerName] = new DashedFiller(helper);
        }
        break;
      case 'zigzag-line':
        if (!fillers[fillerName]) {
          fillers[fillerName] = new ZigZagLineFiller(helper);
        }
        break;
      case 'hachure':
      default:
        fillerName = 'hachure';
        if (!fillers[fillerName]) {
          fillers[fillerName] = new HachureFiller(helper);
        }
        break;
    }
  }
  return fillers[fillerName];
}

function randomSeed() {
  return Math.floor(Math.random() * 2 ** 31);
}
class Random {
  constructor(seed) {
    this.seed = seed;
  }
  next() {
    if (this.seed) {
      return ((2 ** 31 - 1) & (this.seed = Math.imul(48271, this.seed))) / 2 ** 31;
    } else {
      return Math.random();
    }
  }
}

const COMMAND = 0;
const NUMBER = 1;
const EOD = 2;
const PARAMS = {
  A: 7,
  a: 7,
  C: 6,
  c: 6,
  H: 1,
  h: 1,
  L: 2,
  l: 2,
  M: 2,
  m: 2,
  Q: 4,
  q: 4,
  S: 4,
  s: 4,
  T: 2,
  t: 2,
  V: 1,
  v: 1,
  Z: 0,
  z: 0,
};
function tokenize(d) {
  const tokens = new Array();
  while (d !== '') {
    if (d.match(/^([ \t\r\n,]+)/)) {
      d = d.substr(RegExp.$1.length);
    } else if (d.match(/^([aAcChHlLmMqQsStTvVzZ])/)) {
      tokens[tokens.length] = { type: COMMAND, text: RegExp.$1 };
      d = d.substr(RegExp.$1.length);
    } else if (d.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/)) {
      tokens[tokens.length] = { type: NUMBER, text: `${parseFloat(RegExp.$1)}` };
      d = d.substr(RegExp.$1.length);
    } else {
      return [];
    }
  }
  tokens[tokens.length] = { type: EOD, text: '' };
  return tokens;
}
function isType(token, type) {
  return token.type === type;
}
function parsePath(d) {
  const segments = [];
  const tokens = tokenize(d);
  let mode = 'BOD';
  let index = 0;
  let token = tokens[index];
  while (!isType(token, EOD)) {
    let paramsCount = 0;
    const params = [];
    if (mode === 'BOD') {
      if (token.text === 'M' || token.text === 'm') {
        index++;
        paramsCount = PARAMS[token.text];
        mode = token.text;
      } else {
        return parsePath('M0,0' + d);
      }
    } else if (isType(token, NUMBER)) {
      paramsCount = PARAMS[mode];
    } else {
      index++;
      paramsCount = PARAMS[token.text];
      mode = token.text;
    }
    if (index + paramsCount < tokens.length) {
      for (let i = index; i < index + paramsCount; i++) {
        const numbeToken = tokens[i];
        if (isType(numbeToken, NUMBER)) {
          params[params.length] = +numbeToken.text;
        } else {
          throw new Error('Param not a number: ' + mode + ',' + numbeToken.text);
        }
      }
      if (typeof PARAMS[mode] === 'number') {
        const segment = { key: mode, data: params };
        segments.push(segment);
        index += paramsCount;
        token = tokens[index];
        if (mode === 'M') mode = 'L';
        if (mode === 'm') mode = 'l';
      } else {
        throw new Error('Bad segment: ' + mode);
      }
    } else {
      throw new Error('Path data ended short');
    }
  }
  return segments;
}

// Translate relative commands to absolute commands
function absolutize(segments) {
  let cx = 0,
    cy = 0;
  let subx = 0,
    suby = 0;
  const out = [];
  for (const { key, data } of segments) {
    switch (key) {
      case 'M':
        out.push({ key: 'M', data: [...data] });
        [cx, cy] = data;
        [subx, suby] = data;
        break;
      case 'm':
        cx += data[0];
        cy += data[1];
        out.push({ key: 'M', data: [cx, cy] });
        subx = cx;
        suby = cy;
        break;
      case 'L':
        out.push({ key: 'L', data: [...data] });
        [cx, cy] = data;
        break;
      case 'l':
        cx += data[0];
        cy += data[1];
        out.push({ key: 'L', data: [cx, cy] });
        break;
      case 'C':
        out.push({ key: 'C', data: [...data] });
        cx = data[4];
        cy = data[5];
        break;
      case 'c': {
        const newdata = data.map((d, i) => (i % 2 ? d + cy : d + cx));
        out.push({ key: 'C', data: newdata });
        cx = newdata[4];
        cy = newdata[5];
        break;
      }
      case 'Q':
        out.push({ key: 'Q', data: [...data] });
        cx = data[2];
        cy = data[3];
        break;
      case 'q': {
        const newdata = data.map((d, i) => (i % 2 ? d + cy : d + cx));
        out.push({ key: 'Q', data: newdata });
        cx = newdata[2];
        cy = newdata[3];
        break;
      }
      case 'A':
        out.push({ key: 'A', data: [...data] });
        cx = data[5];
        cy = data[6];
        break;
      case 'a':
        cx += data[5];
        cy += data[6];
        out.push({ key: 'A', data: [data[0], data[1], data[2], data[3], data[4], cx, cy] });
        break;
      case 'H':
        out.push({ key: 'H', data: [...data] });
        cx = data[0];
        break;
      case 'h':
        cx += data[0];
        out.push({ key: 'H', data: [cx] });
        break;
      case 'V':
        out.push({ key: 'V', data: [...data] });
        cy = data[0];
        break;
      case 'v':
        cy += data[0];
        out.push({ key: 'V', data: [cy] });
        break;
      case 'S':
        out.push({ key: 'S', data: [...data] });
        cx = data[2];
        cy = data[3];
        break;
      case 's': {
        const newdata = data.map((d, i) => (i % 2 ? d + cy : d + cx));
        out.push({ key: 'S', data: newdata });
        cx = newdata[2];
        cy = newdata[3];
        break;
      }
      case 'T':
        out.push({ key: 'T', data: [...data] });
        cx = data[0];
        cy = data[1];
        break;
      case 't':
        cx += data[0];
        cy += data[1];
        out.push({ key: 'T', data: [cx, cy] });
        break;
      case 'Z':
      case 'z':
        out.push({ key: 'Z', data: [] });
        cx = subx;
        cy = suby;
        break;
    }
  }
  return out;
}

// Normalize path to include only M, L, C, and Z commands
function normalize(segments) {
  const out = [];
  let lastType = '';
  let cx = 0,
    cy = 0;
  let subx = 0,
    suby = 0;
  let lcx = 0,
    lcy = 0;
  for (const { key, data } of segments) {
    switch (key) {
      case 'M':
        out.push({ key: 'M', data: [...data] });
        [cx, cy] = data;
        [subx, suby] = data;
        break;
      case 'C':
        out.push({ key: 'C', data: [...data] });
        cx = data[4];
        cy = data[5];
        lcx = data[2];
        lcy = data[3];
        break;
      case 'L':
        out.push({ key: 'L', data: [...data] });
        [cx, cy] = data;
        break;
      case 'H':
        cx = data[0];
        out.push({ key: 'L', data: [cx, cy] });
        break;
      case 'V':
        cy = data[0];
        out.push({ key: 'L', data: [cx, cy] });
        break;
      case 'S': {
        let cx1 = 0,
          cy1 = 0;
        if (lastType === 'C' || lastType === 'S') {
          cx1 = cx + (cx - lcx);
          cy1 = cy + (cy - lcy);
        } else {
          cx1 = cx;
          cy1 = cy;
        }
        out.push({ key: 'C', data: [cx1, cy1, ...data] });
        lcx = data[0];
        lcy = data[1];
        cx = data[2];
        cy = data[3];
        break;
      }
      case 'T': {
        const [x, y] = data;
        let x1 = 0,
          y1 = 0;
        if (lastType === 'Q' || lastType === 'T') {
          x1 = cx + (cx - lcx);
          y1 = cy + (cy - lcy);
        } else {
          x1 = cx;
          y1 = cy;
        }
        const cx1 = cx + (2 * (x1 - cx)) / 3;
        const cy1 = cy + (2 * (y1 - cy)) / 3;
        const cx2 = x + (2 * (x1 - x)) / 3;
        const cy2 = y + (2 * (y1 - y)) / 3;
        out.push({ key: 'C', data: [cx1, cy1, cx2, cy2, x, y] });
        lcx = x1;
        lcy = y1;
        cx = x;
        cy = y;
        break;
      }
      case 'Q': {
        const [x1, y1, x, y] = data;
        const cx1 = cx + (2 * (x1 - cx)) / 3;
        const cy1 = cy + (2 * (y1 - cy)) / 3;
        const cx2 = x + (2 * (x1 - x)) / 3;
        const cy2 = y + (2 * (y1 - y)) / 3;
        out.push({ key: 'C', data: [cx1, cy1, cx2, cy2, x, y] });
        lcx = x1;
        lcy = y1;
        cx = x;
        cy = y;
        break;
      }
      case 'A': {
        const r1 = Math.abs(data[0]);
        const r2 = Math.abs(data[1]);
        const angle = data[2];
        const largeArcFlag = data[3];
        const sweepFlag = data[4];
        const x = data[5];
        const y = data[6];
        if (r1 === 0 || r2 === 0) {
          out.push({ key: 'C', data: [cx, cy, x, y, x, y] });
          cx = x;
          cy = y;
        } else {
          if (cx !== x || cy !== y) {
            const curves = arcToCubicCurves(cx, cy, x, y, r1, r2, angle, largeArcFlag, sweepFlag);
            curves.forEach(function (curve) {
              out.push({ key: 'C', data: curve });
            });
            cx = x;
            cy = y;
          }
        }
        break;
      }
      case 'Z':
        out.push({ key: 'Z', data: [] });
        cx = subx;
        cy = suby;
        break;
    }
    lastType = key;
  }
  return out;
}
function degToRad(degrees) {
  return (Math.PI * degrees) / 180;
}
function rotate(x, y, angleRad) {
  const X = x * Math.cos(angleRad) - y * Math.sin(angleRad);
  const Y = x * Math.sin(angleRad) + y * Math.cos(angleRad);
  return [X, Y];
}
function arcToCubicCurves(x1, y1, x2, y2, r1, r2, angle, largeArcFlag, sweepFlag, recursive) {
  const angleRad = degToRad(angle);
  let params = [];
  let f1 = 0,
    f2 = 0,
    cx = 0,
    cy = 0;
  if (recursive) {
    [f1, f2, cx, cy] = recursive;
  } else {
    [x1, y1] = rotate(x1, y1, -angleRad);
    [x2, y2] = rotate(x2, y2, -angleRad);
    const x = (x1 - x2) / 2;
    const y = (y1 - y2) / 2;
    let h = (x * x) / (r1 * r1) + (y * y) / (r2 * r2);
    if (h > 1) {
      h = Math.sqrt(h);
      r1 = h * r1;
      r2 = h * r2;
    }
    const sign = largeArcFlag === sweepFlag ? -1 : 1;
    const r1Pow = r1 * r1;
    const r2Pow = r2 * r2;
    const left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x;
    const right = r1Pow * y * y + r2Pow * x * x;
    const k = sign * Math.sqrt(Math.abs(left / right));
    cx = (k * r1 * y) / r2 + (x1 + x2) / 2;
    cy = (k * -r2 * x) / r1 + (y1 + y2) / 2;
    f1 = Math.asin(parseFloat(((y1 - cy) / r2).toFixed(9)));
    f2 = Math.asin(parseFloat(((y2 - cy) / r2).toFixed(9)));
    if (x1 < cx) {
      f1 = Math.PI - f1;
    }
    if (x2 < cx) {
      f2 = Math.PI - f2;
    }
    if (f1 < 0) {
      f1 = Math.PI * 2 + f1;
    }
    if (f2 < 0) {
      f2 = Math.PI * 2 + f2;
    }
    if (sweepFlag && f1 > f2) {
      f1 = f1 - Math.PI * 2;
    }
    if (!sweepFlag && f2 > f1) {
      f2 = f2 - Math.PI * 2;
    }
  }
  let df = f2 - f1;
  if (Math.abs(df) > (Math.PI * 120) / 180) {
    const f2old = f2;
    const x2old = x2;
    const y2old = y2;
    if (sweepFlag && f2 > f1) {
      f2 = f1 + ((Math.PI * 120) / 180) * 1;
    } else {
      f2 = f1 + ((Math.PI * 120) / 180) * -1;
    }
    x2 = cx + r1 * Math.cos(f2);
    y2 = cy + r2 * Math.sin(f2);
    params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r2, angle, 0, sweepFlag, [f2, f2old, cx, cy]);
  }
  df = f2 - f1;
  const c1 = Math.cos(f1);
  const s1 = Math.sin(f1);
  const c2 = Math.cos(f2);
  const s2 = Math.sin(f2);
  const t = Math.tan(df / 4);
  const hx = (4 / 3) * r1 * t;
  const hy = (4 / 3) * r2 * t;
  const m1 = [x1, y1];
  const m2 = [x1 + hx * s1, y1 - hy * c1];
  const m3 = [x2 + hx * s2, y2 - hy * c2];
  const m4 = [x2, y2];
  m2[0] = 2 * m1[0] - m2[0];
  m2[1] = 2 * m1[1] - m2[1];
  if (recursive) {
    return [m2, m3, m4].concat(params);
  } else {
    params = [m2, m3, m4].concat(params);
    const curves = [];
    for (let i = 0; i < params.length; i += 3) {
      const r1 = rotate(params[i][0], params[i][1], angleRad);
      const r2 = rotate(params[i + 1][0], params[i + 1][1], angleRad);
      const r3 = rotate(params[i + 2][0], params[i + 2][1], angleRad);
      curves.push([r1[0], r1[1], r2[0], r2[1], r3[0], r3[1]]);
    }
    return curves;
  }
}

const helper = {
  randOffset,
  randOffsetWithRange,
  ellipse,
  doubleLineOps: doubleLineFillOps,
};
function line(x1, y1, x2, y2, o) {
  return { type: 'path', ops: _doubleLine(x1, y1, x2, y2, o) };
}
function linearPath(points, close, o) {
  const len = (points || []).length;
  if (len > 2) {
    const ops = [];
    for (let i = 0; i < len - 1; i++) {
      ops.push(..._doubleLine(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o));
    }
    if (close) {
      ops.push(..._doubleLine(points[len - 1][0], points[len - 1][1], points[0][0], points[0][1], o));
    }
    return { type: 'path', ops };
  } else if (len === 2) {
    return line(points[0][0], points[0][1], points[1][0], points[1][1], o);
  }
  return { type: 'path', ops: [] };
}
function polygon(points, o) {
  return linearPath(points, true, o);
}
function rectangle(x, y, width, height, o) {
  const points = [
    [x, y],
    [x + width, y],
    [x + width, y + height],
    [x, y + height],
  ];
  return polygon(points, o);
}
function curve(points, o) {
  let o1 = _curveWithOffset(points, 1 * (1 + o.roughness * 0.2), o);
  if (!o.disableMultiStroke) {
    const o2 = _curveWithOffset(points, 1.5 * (1 + o.roughness * 0.22), cloneOptionsAlterSeed(o));
    o1 = o1.concat(o2);
  }
  return { type: 'path', ops: o1 };
}
function ellipse(x, y, width, height, o) {
  const params = generateEllipseParams(width, height, o);
  return ellipseWithParams(x, y, o, params).opset;
}
function generateEllipseParams(width, height, o) {
  const psq = Math.sqrt(Math.PI * 2 * Math.sqrt((Math.pow(width / 2, 2) + Math.pow(height / 2, 2)) / 2));
  const stepCount = Math.ceil(Math.max(o.curveStepCount, (o.curveStepCount / Math.sqrt(200)) * psq));
  const increment = (Math.PI * 2) / stepCount;
  let rx = Math.abs(width / 2);
  let ry = Math.abs(height / 2);
  const curveFitRandomness = 1 - o.curveFitting;
  rx += _offsetOpt(rx * curveFitRandomness, o);
  ry += _offsetOpt(ry * curveFitRandomness, o);
  return { increment, rx, ry };
}
function ellipseWithParams(x, y, o, ellipseParams) {
  const [ap1, cp1] = _computeEllipsePoints(
    ellipseParams.increment,
    x,
    y,
    ellipseParams.rx,
    ellipseParams.ry,
    1,
    ellipseParams.increment * _offset(0.1, _offset(0.4, 1, o), o),
    o
  );
  let o1 = _curve(ap1, null, o);
  if (!o.disableMultiStroke && o.roughness !== 0) {
    const [ap2] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1.5, 0, o);
    const o2 = _curve(ap2, null, o);
    o1 = o1.concat(o2);
  }
  return {
    estimatedPoints: cp1,
    opset: { type: 'path', ops: o1 },
  };
}
function arc(x, y, width, height, start, stop, closed, roughClosure, o) {
  const cx = x;
  const cy = y;
  let rx = Math.abs(width / 2);
  let ry = Math.abs(height / 2);
  rx += _offsetOpt(rx * 0.01, o);
  ry += _offsetOpt(ry * 0.01, o);
  let strt = start;
  let stp = stop;
  while (strt < 0) {
    strt += Math.PI * 2;
    stp += Math.PI * 2;
  }
  if (stp - strt > Math.PI * 2) {
    strt = 0;
    stp = Math.PI * 2;
  }
  const ellipseInc = (Math.PI * 2) / o.curveStepCount;
  const arcInc = Math.min(ellipseInc / 2, (stp - strt) / 2);
  const ops = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1, o);
  if (!o.disableMultiStroke) {
    const o2 = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1.5, o);
    ops.push(...o2);
  }
  if (closed) {
    if (roughClosure) {
      ops.push(
        ..._doubleLine(cx, cy, cx + rx * Math.cos(strt), cy + ry * Math.sin(strt), o),
        ..._doubleLine(cx, cy, cx + rx * Math.cos(stp), cy + ry * Math.sin(stp), o)
      );
    } else {
      ops.push(
        { op: 'lineTo', data: [cx, cy] },
        { op: 'lineTo', data: [cx + rx * Math.cos(strt), cy + ry * Math.sin(strt)] }
      );
    }
  }
  return { type: 'path', ops };
}
function svgPath(path, o) {
  const segments = normalize(absolutize(parsePath(path)));
  const ops = [];
  let first = [0, 0];
  let current = [0, 0];
  for (const { key, data } of segments) {
    switch (key) {
      case 'M': {
        const ro = 1 * (o.maxRandomnessOffset || 0);
        const pv = o.preserveVertices;
        ops.push({ op: 'move', data: data.map((d) => d + (pv ? 0 : _offsetOpt(ro, o))) });
        current = [data[0], data[1]];
        first = [data[0], data[1]];
        break;
      }
      case 'L':
        ops.push(..._doubleLine(current[0], current[1], data[0], data[1], o));
        current = [data[0], data[1]];
        break;
      case 'C': {
        const [x1, y1, x2, y2, x, y] = data;
        ops.push(..._bezierTo(x1, y1, x2, y2, x, y, current, o));
        current = [x, y];
        break;
      }
      case 'Z':
        ops.push(..._doubleLine(current[0], current[1], first[0], first[1], o));
        current = [first[0], first[1]];
        break;
    }
  }
  return { type: 'path', ops };
}
// Fills
function solidFillPolygon(polygonList, o) {
  const ops = [];
  for (const points of polygonList) {
    if (points.length) {
      const offset = o.maxRandomnessOffset || 0;
      const len = points.length;
      if (len > 2) {
        ops.push({ op: 'move', data: [points[0][0] + _offsetOpt(offset, o), points[0][1] + _offsetOpt(offset, o)] });
        for (let i = 1; i < len; i++) {
          ops.push({
            op: 'lineTo',
            data: [points[i][0] + _offsetOpt(offset, o), points[i][1] + _offsetOpt(offset, o)],
          });
        }
      }
    }
  }
  return { type: 'fillPath', ops };
}
function patternFillPolygons(polygonList, o) {
  return getFiller(o, helper).fillPolygons(polygonList, o);
}
function patternFillArc(x, y, width, height, start, stop, o) {
  const cx = x;
  const cy = y;
  let rx = Math.abs(width / 2);
  let ry = Math.abs(height / 2);
  rx += _offsetOpt(rx * 0.01, o);
  ry += _offsetOpt(ry * 0.01, o);
  let strt = start;
  let stp = stop;
  while (strt < 0) {
    strt += Math.PI * 2;
    stp += Math.PI * 2;
  }
  if (stp - strt > Math.PI * 2) {
    strt = 0;
    stp = Math.PI * 2;
  }
  const increment = (stp - strt) / o.curveStepCount;
  const points = [];
  for (let angle = strt; angle <= stp; angle = angle + increment) {
    points.push([cx + rx * Math.cos(angle), cy + ry * Math.sin(angle)]);
  }
  points.push([cx + rx * Math.cos(stp), cy + ry * Math.sin(stp)]);
  points.push([cx, cy]);
  return patternFillPolygons([points], o);
}
function randOffset(x, o) {
  return _offsetOpt(x, o);
}
function randOffsetWithRange(min, max, o) {
  return _offset(min, max, o);
}
function doubleLineFillOps(x1, y1, x2, y2, o) {
  return _doubleLine(x1, y1, x2, y2, o, true);
}
// Private helpers
function cloneOptionsAlterSeed(ops) {
  const result = Object.assign({}, ops);
  result.randomizer = undefined;
  if (ops.seed) {
    result.seed = ops.seed + 1;
  }
  return result;
}
function random(ops) {
  if (!ops.randomizer) {
    ops.randomizer = new Random(ops.seed || 0);
  }
  return ops.randomizer.next();
}
function _offset(min, max, ops, roughnessGain = 1) {
  return ops.roughness * roughnessGain * (random(ops) * (max - min) + min);
}
function _offsetOpt(x, ops, roughnessGain = 1) {
  return _offset(-x, x, ops, roughnessGain);
}
function _doubleLine(x1, y1, x2, y2, o, filling = false) {
  const singleStroke = filling ? o.disableMultiStrokeFill : o.disableMultiStroke;
  const o1 = _line(x1, y1, x2, y2, o, true, false);
  if (singleStroke) {
    return o1;
  }
  const o2 = _line(x1, y1, x2, y2, o, true, true);
  return o1.concat(o2);
}
function _line(x1, y1, x2, y2, o, move, overlay) {
  const lengthSq = Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
  const length = Math.sqrt(lengthSq);
  let roughnessGain = 1;
  if (length < 200) {
    roughnessGain = 1;
  } else if (length > 500) {
    roughnessGain = 0.4;
  } else {
    roughnessGain = -0.0016668 * length + 1.233334;
  }
  let offset = o.maxRandomnessOffset || 0;
  if (offset * offset * 100 > lengthSq) {
    offset = length / 10;
  }
  const halfOffset = offset / 2;
  const divergePoint = 0.2 + random(o) * 0.2;
  let midDispX = (o.bowing * o.maxRandomnessOffset * (y2 - y1)) / 200;
  let midDispY = (o.bowing * o.maxRandomnessOffset * (x1 - x2)) / 200;
  midDispX = _offsetOpt(midDispX, o, roughnessGain);
  midDispY = _offsetOpt(midDispY, o, roughnessGain);
  const ops = [];
  const randomHalf = () => _offsetOpt(halfOffset, o, roughnessGain);
  const randomFull = () => _offsetOpt(offset, o, roughnessGain);
  const preserveVertices = o.preserveVertices;
  if (move) {
    if (overlay) {
      ops.push({
        op: 'move',
        data: [x1 + (preserveVertices ? 0 : randomHalf()), y1 + (preserveVertices ? 0 : randomHalf())],
      });
    } else {
      ops.push({
        op: 'move',
        data: [
          x1 + (preserveVertices ? 0 : _offsetOpt(offset, o, roughnessGain)),
          y1 + (preserveVertices ? 0 : _offsetOpt(offset, o, roughnessGain)),
        ],
      });
    }
  }
  if (overlay) {
    ops.push({
      op: 'bcurveTo',
      data: [
        midDispX + x1 + (x2 - x1) * divergePoint + randomHalf(),
        midDispY + y1 + (y2 - y1) * divergePoint + randomHalf(),
        midDispX + x1 + 2 * (x2 - x1) * divergePoint + randomHalf(),
        midDispY + y1 + 2 * (y2 - y1) * divergePoint + randomHalf(),
        x2 + (preserveVertices ? 0 : randomHalf()),
        y2 + (preserveVertices ? 0 : randomHalf()),
      ],
    });
  } else {
    ops.push({
      op: 'bcurveTo',
      data: [
        midDispX + x1 + (x2 - x1) * divergePoint + randomFull(),
        midDispY + y1 + (y2 - y1) * divergePoint + randomFull(),
        midDispX + x1 + 2 * (x2 - x1) * divergePoint + randomFull(),
        midDispY + y1 + 2 * (y2 - y1) * divergePoint + randomFull(),
        x2 + (preserveVertices ? 0 : randomFull()),
        y2 + (preserveVertices ? 0 : randomFull()),
      ],
    });
  }
  return ops;
}
function _curveWithOffset(points, offset, o) {
  const ps = [];
  ps.push([points[0][0] + _offsetOpt(offset, o), points[0][1] + _offsetOpt(offset, o)]);
  ps.push([points[0][0] + _offsetOpt(offset, o), points[0][1] + _offsetOpt(offset, o)]);
  for (let i = 1; i < points.length; i++) {
    ps.push([points[i][0] + _offsetOpt(offset, o), points[i][1] + _offsetOpt(offset, o)]);
    if (i === points.length - 1) {
      ps.push([points[i][0] + _offsetOpt(offset, o), points[i][1] + _offsetOpt(offset, o)]);
    }
  }
  return _curve(ps, null, o);
}
function _curve(points, closePoint, o) {
  const len = points.length;
  const ops = [];
  if (len > 3) {
    const b = [];
    const s = 1 - o.curveTightness;
    ops.push({ op: 'move', data: [points[1][0], points[1][1]] });
    for (let i = 1; i + 2 < len; i++) {
      const cachedVertArray = points[i];
      b[0] = [cachedVertArray[0], cachedVertArray[1]];
      b[1] = [
        cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6,
        cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6,
      ];
      b[2] = [
        points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6,
        points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6,
      ];
      b[3] = [points[i + 1][0], points[i + 1][1]];
      ops.push({ op: 'bcurveTo', data: [b[1][0], b[1][1], b[2][0], b[2][1], b[3][0], b[3][1]] });
    }
    if (closePoint && closePoint.length === 2) {
      const ro = o.maxRandomnessOffset;
      ops.push({ op: 'lineTo', data: [closePoint[0] + _offsetOpt(ro, o), closePoint[1] + _offsetOpt(ro, o)] });
    }
  } else if (len === 3) {
    ops.push({ op: 'move', data: [points[1][0], points[1][1]] });
    ops.push({
      op: 'bcurveTo',
      data: [points[1][0], points[1][1], points[2][0], points[2][1], points[2][0], points[2][1]],
    });
  } else if (len === 2) {
    ops.push(..._doubleLine(points[0][0], points[0][1], points[1][0], points[1][1], o));
  }
  return ops;
}
function _computeEllipsePoints(increment, cx, cy, rx, ry, offset, overlap, o) {
  const coreOnly = o.roughness === 0;
  const corePoints = [];
  const allPoints = [];
  if (coreOnly) {
    increment = increment / 4;
    allPoints.push([cx + rx * Math.cos(-increment), cy + ry * Math.sin(-increment)]);
    for (let angle = 0; angle <= Math.PI * 2; angle = angle + increment) {
      const p = [cx + rx * Math.cos(angle), cy + ry * Math.sin(angle)];
      corePoints.push(p);
      allPoints.push(p);
    }
    allPoints.push([cx + rx * Math.cos(0), cy + ry * Math.sin(0)]);
    allPoints.push([cx + rx * Math.cos(increment), cy + ry * Math.sin(increment)]);
  } else {
    const radOffset = _offsetOpt(0.5, o) - Math.PI / 2;
    allPoints.push([
      _offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
      _offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment),
    ]);
    const endAngle = Math.PI * 2 + radOffset - 0.01;
    for (let angle = radOffset; angle < endAngle; angle = angle + increment) {
      const p = [_offsetOpt(offset, o) + cx + rx * Math.cos(angle), _offsetOpt(offset, o) + cy + ry * Math.sin(angle)];
      corePoints.push(p);
      allPoints.push(p);
    }
    allPoints.push([
      _offsetOpt(offset, o) + cx + rx * Math.cos(radOffset + Math.PI * 2 + overlap * 0.5),
      _offsetOpt(offset, o) + cy + ry * Math.sin(radOffset + Math.PI * 2 + overlap * 0.5),
    ]);
    allPoints.push([
      _offsetOpt(offset, o) + cx + 0.98 * rx * Math.cos(radOffset + overlap),
      _offsetOpt(offset, o) + cy + 0.98 * ry * Math.sin(radOffset + overlap),
    ]);
    allPoints.push([
      _offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset + overlap * 0.5),
      _offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset + overlap * 0.5),
    ]);
  }
  return [allPoints, corePoints];
}
function _arc(increment, cx, cy, rx, ry, strt, stp, offset, o) {
  const radOffset = strt + _offsetOpt(0.1, o);
  const points = [];
  points.push([
    _offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
    _offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment),
  ]);
  for (let angle = radOffset; angle <= stp; angle = angle + increment) {
    points.push([_offsetOpt(offset, o) + cx + rx * Math.cos(angle), _offsetOpt(offset, o) + cy + ry * Math.sin(angle)]);
  }
  points.push([cx + rx * Math.cos(stp), cy + ry * Math.sin(stp)]);
  points.push([cx + rx * Math.cos(stp), cy + ry * Math.sin(stp)]);
  return _curve(points, null, o);
}
function _bezierTo(x1, y1, x2, y2, x, y, current, o) {
  const ops = [];
  const ros = [o.maxRandomnessOffset || 1, (o.maxRandomnessOffset || 1) + 0.3];
  let f = [0, 0];
  const iterations = o.disableMultiStroke ? 1 : 2;
  const preserveVertices = o.preserveVertices;
  for (let i = 0; i < iterations; i++) {
    if (i === 0) {
      ops.push({ op: 'move', data: [current[0], current[1]] });
    } else {
      ops.push({
        op: 'move',
        data: [
          current[0] + (preserveVertices ? 0 : _offsetOpt(ros[0], o)),
          current[1] + (preserveVertices ? 0 : _offsetOpt(ros[0], o)),
        ],
      });
    }
    f = preserveVertices ? [x, y] : [x + _offsetOpt(ros[i], o), y + _offsetOpt(ros[i], o)];
    ops.push({
      op: 'bcurveTo',
      data: [
        x1 + _offsetOpt(ros[i], o),
        y1 + _offsetOpt(ros[i], o),
        x2 + _offsetOpt(ros[i], o),
        y2 + _offsetOpt(ros[i], o),
        f[0],
        f[1],
      ],
    });
  }
  return ops;
}

function clone(p) {
  return [...p];
}
function curveToBezier(pointsIn, curveTightness = 0) {
  const len = pointsIn.length;
  if (len < 3) {
    throw new Error('A curve must have at least three points.');
  }
  const out = [];
  if (len === 3) {
    out.push(clone(pointsIn[0]), clone(pointsIn[1]), clone(pointsIn[2]), clone(pointsIn[2]));
  } else {
    const points = [];
    points.push(pointsIn[0], pointsIn[0]);
    for (let i = 1; i < pointsIn.length; i++) {
      points.push(pointsIn[i]);
      if (i === pointsIn.length - 1) {
        points.push(pointsIn[i]);
      }
    }
    const b = [];
    const s = 1 - curveTightness;
    out.push(clone(points[0]));
    for (let i = 1; i + 2 < points.length; i++) {
      const cachedVertArray = points[i];
      b[0] = [cachedVertArray[0], cachedVertArray[1]];
      b[1] = [
        cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6,
        cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6,
      ];
      b[2] = [
        points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6,
        points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6,
      ];
      b[3] = [points[i + 1][0], points[i + 1][1]];
      out.push(b[1], b[2], b[3]);
    }
  }
  return out;
}

// distance between 2 points
function distance(p1, p2) {
  return Math.sqrt(distanceSq(p1, p2));
}
// distance between 2 points squared
function distanceSq(p1, p2) {
  return Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2);
}
// Sistance squared from a point p to the line segment vw
function distanceToSegmentSq(p, v, w) {
  const l2 = distanceSq(v, w);
  if (l2 === 0) {
    return distanceSq(p, v);
  }
  let t = ((p[0] - v[0]) * (w[0] - v[0]) + (p[1] - v[1]) * (w[1] - v[1])) / l2;
  t = Math.max(0, Math.min(1, t));
  return distanceSq(p, lerp(v, w, t));
}
function lerp(a, b, t) {
  return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
}
// Adapted from https://seant23.wordpress.com/2010/11/12/offset-bezier-curves/
function flatness(points, offset) {
  const p1 = points[offset + 0];
  const p2 = points[offset + 1];
  const p3 = points[offset + 2];
  const p4 = points[offset + 3];
  let ux = 3 * p2[0] - 2 * p1[0] - p4[0];
  ux *= ux;
  let uy = 3 * p2[1] - 2 * p1[1] - p4[1];
  uy *= uy;
  let vx = 3 * p3[0] - 2 * p4[0] - p1[0];
  vx *= vx;
  let vy = 3 * p3[1] - 2 * p4[1] - p1[1];
  vy *= vy;
  if (ux < vx) {
    ux = vx;
  }
  if (uy < vy) {
    uy = vy;
  }
  return ux + uy;
}
function getPointsOnBezierCurveWithSplitting(points, offset, tolerance, newPoints) {
  const outPoints = newPoints || [];
  if (flatness(points, offset) < tolerance) {
    const p0 = points[offset + 0];
    if (outPoints.length) {
      const d = distance(outPoints[outPoints.length - 1], p0);
      if (d > 1) {
        outPoints.push(p0);
      }
    } else {
      outPoints.push(p0);
    }
    outPoints.push(points[offset + 3]);
  } else {
    // subdivide
    const t = 0.5;
    const p1 = points[offset + 0];
    const p2 = points[offset + 1];
    const p3 = points[offset + 2];
    const p4 = points[offset + 3];
    const q1 = lerp(p1, p2, t);
    const q2 = lerp(p2, p3, t);
    const q3 = lerp(p3, p4, t);
    const r1 = lerp(q1, q2, t);
    const r2 = lerp(q2, q3, t);
    const red = lerp(r1, r2, t);
    getPointsOnBezierCurveWithSplitting([p1, q1, r1, red], 0, tolerance, outPoints);
    getPointsOnBezierCurveWithSplitting([red, r2, q3, p4], 0, tolerance, outPoints);
  }
  return outPoints;
}
function simplify(points, distance) {
  return simplifyPoints(points, 0, points.length, distance);
}
// Ramer–Douglas–Peucker algorithm
// https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
function simplifyPoints(points, start, end, epsilon, newPoints) {
  const outPoints = newPoints || [];
  // find the most distance point from the endpoints
  const s = points[start];
  const e = points[end - 1];
  let maxDistSq = 0;
  let maxNdx = 1;
  for (let i = start + 1; i < end - 1; ++i) {
    const distSq = distanceToSegmentSq(points[i], s, e);
    if (distSq > maxDistSq) {
      maxDistSq = distSq;
      maxNdx = i;
    }
  }
  // if that point is too far, split
  if (Math.sqrt(maxDistSq) > epsilon) {
    simplifyPoints(points, start, maxNdx + 1, epsilon, outPoints);
    simplifyPoints(points, maxNdx, end, epsilon, outPoints);
  } else {
    if (!outPoints.length) {
      outPoints.push(s);
    }
    outPoints.push(e);
  }
  return outPoints;
}
function pointsOnBezierCurves(points, tolerance = 0.15, distance) {
  const newPoints = [];
  const numSegments = (points.length - 1) / 3;
  for (let i = 0; i < numSegments; i++) {
    const offset = i * 3;
    getPointsOnBezierCurveWithSplitting(points, offset, tolerance, newPoints);
  }
  if (distance && distance > 0) {
    return simplifyPoints(newPoints, 0, newPoints.length, distance);
  }
  return newPoints;
}

function pointsOnPath(path, tolerance, distance) {
  const segments = parsePath(path);
  const normalized = normalize(absolutize(segments));
  const sets = [];
  let currentPoints = [];
  let start = [0, 0];
  let pendingCurve = [];
  const appendPendingCurve = () => {
    if (pendingCurve.length >= 4) {
      currentPoints.push(...pointsOnBezierCurves(pendingCurve, tolerance));
    }
    pendingCurve = [];
  };
  const appendPendingPoints = () => {
    appendPendingCurve();
    if (currentPoints.length) {
      sets.push(currentPoints);
      currentPoints = [];
    }
  };
  for (const { key, data } of normalized) {
    switch (key) {
      case 'M':
        appendPendingPoints();
        start = [data[0], data[1]];
        currentPoints.push(start);
        break;
      case 'L':
        appendPendingCurve();
        currentPoints.push([data[0], data[1]]);
        break;
      case 'C':
        if (!pendingCurve.length) {
          const lastPoint = currentPoints.length ? currentPoints[currentPoints.length - 1] : start;
          pendingCurve.push([lastPoint[0], lastPoint[1]]);
        }
        pendingCurve.push([data[0], data[1]]);
        pendingCurve.push([data[2], data[3]]);
        pendingCurve.push([data[4], data[5]]);
        break;
      case 'Z':
        appendPendingCurve();
        currentPoints.push([start[0], start[1]]);
        break;
    }
  }
  appendPendingPoints();
  if (!distance) {
    return sets;
  }
  const out = [];
  for (const set of sets) {
    const simplifiedSet = simplify(set, distance);
    if (simplifiedSet.length) {
      out.push(simplifiedSet);
    }
  }
  return out;
}

const NOS = 'none';
class RoughGenerator {
  constructor(config) {
    this.defaultOptions = {
      maxRandomnessOffset: 2,
      roughness: 1,
      bowing: 1,
      stroke: '#000',
      strokeWidth: 1,
      curveTightness: 0,
      curveFitting: 0.95,
      curveStepCount: 9,
      fillStyle: 'hachure',
      fillWeight: -1,
      hachureAngle: -41,
      hachureGap: -1,
      dashOffset: -1,
      dashGap: -1,
      zigzagOffset: -1,
      seed: 0,
      disableMultiStroke: false,
      disableMultiStrokeFill: false,
      preserveVertices: false,
    };
    this.config = config || {};
    if (this.config.options) {
      this.defaultOptions = this._o(this.config.options);
    }
  }
  static newSeed() {
    return randomSeed();
  }
  _o(options) {
    return options ? Object.assign({}, this.defaultOptions, options) : this.defaultOptions;
  }
  _d(shape, sets, options) {
    return { shape, sets: sets || [], options: options || this.defaultOptions };
  }
  line(x1, y1, x2, y2, options) {
    const o = this._o(options);
    return this._d('line', [line(x1, y1, x2, y2, o)], o);
  }
  rectangle(x, y, width, height, options) {
    const o = this._o(options);
    const paths = [];
    const outline = rectangle(x, y, width, height, o);
    if (o.fill) {
      const points = [
        [x, y],
        [x + width, y],
        [x + width, y + height],
        [x, y + height],
      ];
      if (o.fillStyle === 'solid') {
        paths.push(solidFillPolygon([points], o));
      } else {
        paths.push(patternFillPolygons([points], o));
      }
    }
    if (o.stroke !== NOS) {
      paths.push(outline);
    }
    return this._d('rectangle', paths, o);
  }
  ellipse(x, y, width, height, options) {
    const o = this._o(options);
    const paths = [];
    const ellipseParams = generateEllipseParams(width, height, o);
    const ellipseResponse = ellipseWithParams(x, y, o, ellipseParams);
    if (o.fill) {
      if (o.fillStyle === 'solid') {
        const shape = ellipseWithParams(x, y, o, ellipseParams).opset;
        shape.type = 'fillPath';
        paths.push(shape);
      } else {
        paths.push(patternFillPolygons([ellipseResponse.estimatedPoints], o));
      }
    }
    if (o.stroke !== NOS) {
      paths.push(ellipseResponse.opset);
    }
    return this._d('ellipse', paths, o);
  }
  circle(x, y, diameter, options) {
    const ret = this.ellipse(x, y, diameter, diameter, options);
    ret.shape = 'circle';
    return ret;
  }
  linearPath(points, options) {
    const o = this._o(options);
    return this._d('linearPath', [linearPath(points, false, o)], o);
  }
  arc(x, y, width, height, start, stop, closed = false, options) {
    const o = this._o(options);
    const paths = [];
    const outline = arc(x, y, width, height, start, stop, closed, true, o);
    if (closed && o.fill) {
      if (o.fillStyle === 'solid') {
        const fillOptions = Object.assign({}, o);
        fillOptions.disableMultiStroke = true;
        const shape = arc(x, y, width, height, start, stop, true, false, fillOptions);
        shape.type = 'fillPath';
        paths.push(shape);
      } else {
        paths.push(patternFillArc(x, y, width, height, start, stop, o));
      }
    }
    if (o.stroke !== NOS) {
      paths.push(outline);
    }
    return this._d('arc', paths, o);
  }
  curve(points, options) {
    const o = this._o(options);
    const paths = [];
    const outline = curve(points, o);
    if (o.fill && o.fill !== NOS && points.length >= 3) {
      const bcurve = curveToBezier(points);
      const polyPoints = pointsOnBezierCurves(bcurve, 10, (1 + o.roughness) / 2);
      if (o.fillStyle === 'solid') {
        paths.push(solidFillPolygon([polyPoints], o));
      } else {
        paths.push(patternFillPolygons([polyPoints], o));
      }
    }
    if (o.stroke !== NOS) {
      paths.push(outline);
    }
    return this._d('curve', paths, o);
  }
  polygon(points, options) {
    const o = this._o(options);
    const paths = [];
    const outline = linearPath(points, true, o);
    if (o.fill) {
      if (o.fillStyle === 'solid') {
        paths.push(solidFillPolygon([points], o));
      } else {
        paths.push(patternFillPolygons([points], o));
      }
    }
    if (o.stroke !== NOS) {
      paths.push(outline);
    }
    return this._d('polygon', paths, o);
  }
  path(d, options) {
    const o = this._o(options);
    const paths = [];
    if (!d) {
      return this._d('path', paths, o);
    }
    d = (d || '').replace(/\n/g, ' ').replace(/(-\s)/g, '-').replace('/(ss)/g', ' ');
    const hasFill = o.fill && o.fill !== 'transparent' && o.fill !== NOS;
    const hasStroke = o.stroke !== NOS;
    const simplified = !!(o.simplification && o.simplification < 1);
    const distance = simplified ? 4 - 4 * o.simplification : (1 + o.roughness) / 2;
    const sets = pointsOnPath(d, 1, distance);
    if (hasFill) {
      if (o.fillStyle === 'solid') {
        paths.push(solidFillPolygon(sets, o));
      } else {
        paths.push(patternFillPolygons(sets, o));
      }
    }
    if (hasStroke) {
      if (simplified) {
        sets.forEach((set) => {
          paths.push(linearPath(set, false, o));
        });
      } else {
        paths.push(svgPath(d, o));
      }
    }
    return this._d('path', paths, o);
  }
  opsToPath(drawing, fixedDecimals) {
    let path = '';
    for (const item of drawing.ops) {
      const data =
        typeof fixedDecimals === 'number' && fixedDecimals >= 0
          ? item.data.map((d) => +d.toFixed(fixedDecimals))
          : item.data;
      switch (item.op) {
        case 'move':
          path += `M${data[0]} ${data[1]} `;
          break;
        case 'bcurveTo':
          path += `C${data[0]} ${data[1]}, ${data[2]} ${data[3]}, ${data[4]} ${data[5]} `;
          break;
        case 'lineTo':
          path += `L${data[0]} ${data[1]} `;
          break;
      }
    }
    return path.trim();
  }
  toPaths(drawable) {
    const sets = drawable.sets || [];
    const o = drawable.options || this.defaultOptions;
    const paths = [];
    for (const drawing of sets) {
      let path = null;
      switch (drawing.type) {
        case 'path':
          path = {
            d: this.opsToPath(drawing),
            stroke: o.stroke,
            strokeWidth: o.strokeWidth,
            fill: NOS,
          };
          break;
        case 'fillPath':
          path = {
            d: this.opsToPath(drawing),
            stroke: NOS,
            strokeWidth: 0,
            fill: o.fill || NOS,
          };
          break;
        case 'fillSketch':
          path = this.fillSketch(drawing, o);
          break;
      }
      if (path) {
        paths.push(path);
      }
    }
    return paths;
  }
  fillSketch(drawing, o) {
    let fweight = o.fillWeight;
    if (fweight < 0) {
      fweight = o.strokeWidth / 2;
    }
    return {
      d: this.opsToPath(drawing),
      stroke: o.fill || NOS,
      strokeWidth: fweight,
      fill: NOS,
    };
  }
}

/**
 * Copyright 2020 Bonitasoft S.A.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
// choose to use RoughCanvas to inherit from the general drawing method
// + get access to Drawable object
class RoughJsAdaptor {
  // constructor(readonly c: mxSvgCanvas2D, config?: Config) {
  constructor(c, config) {
    this.c = c;
    this.config = config;
    this.gen = new RoughGenerator(config);
  }
  // implementation taken from the draw.io adaptation
  draw(drawable) {
    const sets = drawable.sets || [];
    const o = drawable.options || this.getDefaultOptions();
    for (let i = 0; i < sets.length; i++) {
      const drawing = sets[i];
      switch (drawing.type) {
        case 'path':
          if (o.stroke != null) {
            this._drawToContext(drawing, o);
          }
          break;
        case 'fillPath':
          this._drawToContext(drawing, o);
          break;
        case 'fillSketch':
          this._fillSketch(drawing, o);
          break;
      }
    }
  }
  // from draw.io
  // TODO add link to github url for the draw.io original code
  _fillSketch(drawing, o) {
    const strokeColor = this.c.state.strokeColor;
    const strokeWidth = this.c.state.strokeWidth;
    const strokeAlpha = this.c.state.strokeAlpha;
    const dashed = this.c.state.dashed;
    const fixDash = this.c.state.fixDash;
    let fweight = o.fillWeight;
    if (fweight < 0) {
      fweight = o.strokeWidth / 2;
    }
    this.c.setStrokeAlpha(this.c.state.fillAlpha);
    this.c.setStrokeColor(o.fill || '');
    this.c.setStrokeWidth(fweight);
    this.c.setDashed(false, fixDash);
    this._drawToContext(drawing, o);
    // this._drawToContext(ctx, drawing, o);
    this.c.setDashed(dashed, fixDash);
    this.c.setStrokeWidth(strokeWidth);
    this.c.setStrokeColor(strokeColor);
    this.c.setStrokeAlpha(strokeAlpha);
  }
  //RoughCanvas
  // private fillSketch(ctx: CanvasRenderingContext2D, drawing: OpSet, o: ResolvedOptions) {
  //   let fweight = o.fillWeight;
  //   if (fweight < 0) {
  //     fweight = o.strokeWidth / 2;
  //   }
  //   ctx.save();
  //   if (o.fillLineDash) {
  //     ctx.setLineDash(o.fillLineDash);
  //   }
  //   if (o.fillLineDashOffset) {
  //     ctx.lineDashOffset = o.fillLineDashOffset;
  //   }
  //   ctx.strokeStyle = o.fill || '';
  //   ctx.lineWidth = fweight;
  //   this._drawToContext(ctx, drawing);
  //   ctx.restore();
  // }
  _drawToContext(drawing, o) {
    this.c.begin();
    for (let i = 0; i < drawing.ops.length; i++) {
      const item = drawing.ops[i];
      const data = item.data;
      switch (item.op) {
        case 'move':
          this.c.moveTo(data[0], data[1]);
          break;
        case 'bcurveTo':
          this.c.curveTo(data[0], data[1], data[2], data[3], data[4], data[5]);
          break;
        case 'lineTo':
          this.c.lineTo(data[0], data[1]);
          break;
      }
    }
    this.c.end();
    if (drawing.type === 'fillPath' && o.fill) {
      this.c.fill();
    } else {
      this.c.stroke();
    }
  }
  // copied from RoughCanvas 4.X.X
  get generator() {
    return this.gen;
  }
  getDefaultOptions() {
    return this.gen.defaultOptions;
  }
  // line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable {
  //   const d = this.gen.line(x1, y1, x2, y2, options);
  //   this.draw(d);
  //   return d;
  // }
  //
  // rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable {
  //   const d = this.gen.rectangle(x, y, width, height, options);
  //   this.draw(d);
  //   return d;
  // }
  //
  // ellipse(x: number, y: number, width: number, height: number, options?: Options): Drawable {
  //   const d = this.gen.ellipse(x, y, width, height, options);
  //   this.draw(d);
  //   return d;
  // }
  //
  // circle(x: number, y: number, diameter: number, options?: Options): Drawable {
  //   const d = this.gen.circle(x, y, diameter, options);
  //   this.draw(d);
  //   return d;
  // }
  //
  // linearPath(points: Point[], options?: Options): Drawable {
  //   const d = this.gen.linearPath(points, options);
  //   this.draw(d);
  //   return d;
  // }
  //
  // polygon(points: Point[], options?: Options): Drawable {
  //   const d = this.gen.polygon(points, options);
  //   this.draw(d);
  //   return d;
  // }
  //
  // arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean = false, options?: Options): Drawable {
  //   const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
  //   this.draw(d);
  //   return d;
  // }
  //
  // curve(points: Point[], options?: Options): Drawable {
  //   const d = this.gen.curve(points, options);
  //   this.draw(d);
  //   return d;
  // }
  //
  path(d, options) {
    const drawing = this.gen.path(d, options);
    this.draw(drawing);
    return drawing;
  }
}
/*

Editor.createRoughCanvas = function(c)
{
var rc = rough.canvas(
  {
    // Provides expected function but return value is not used
    getContext: function()
    {
      return c;
    }
  });

rc.draw = function(drawable)
{
  var sets = drawable.sets || [];
  var o = drawable.options || this.getDefaultOptions();

  for (var i = 0; i < sets.length; i++)
  {
    var drawing = sets[i];

    switch (drawing.type)
    {
      case 'path':
        if (o.stroke != null)
        {
          this._drawToContext(c, drawing, o);
        }
        break;
      case 'fillPath':
        this._drawToContext(c, drawing, o);
        break;
      case 'fillSketch':
        this.fillSketch(c, drawing, o);
        break;
    }
  }
};
*/
/*
rc.fillSketch = function(ctx, drawing, o)
{
  var strokeColor = this.c.state.strokeColor;
  var strokeWidth = this.c.state.strokeWidth;
  var strokeAlpha = this.c.state.strokeAlpha;
  var dashed = this.c.state.dashed;

  var fweight = o.fillWeight;
  if (fweight < 0)
  {
    fweight = o.strokeWidth / 2;
  }

  this.c.setStrokeAlpha(c.state.fillAlpha);
  this.c.setStrokeColor(o.fill || '');
  this.c.setStrokeWidth(fweight);
  this.c.setDashed(false);

  this._drawToContext(ctx, drawing, o);

  this.c.setDashed(dashed);
  this.c.setStrokeWidth(strokeWidth);
  this.c.setStrokeColor(strokeColor);
  this.c.setStrokeAlpha(strokeAlpha);
};

rc._drawToContext = function(ctx, drawing, o)
{
  ctx.begin();

  for (var i = 0; i < drawing.ops.length; i++)
  {
    var item = drawing.ops[i];
    var data = item.data;

    switch (item.op)
    {
      case 'move':
        ctx.moveTo(data[0], data[1]);
        break;
      case 'bcurveTo':
        ctx.curveTo(data[0], data[1], data[2], data[3], data[4], data[5]);
        break;
      case 'lineTo':
        ctx.lineTo(data[0], data[1]);
        break;
    }
  };

  ctx.end();

  if (drawing.type === 'fillPath' && o.filled)
  {
    ctx.fill();
  }
  else
  {
    ctx.stroke();
  }
};

return rc;
};
*/
/*
private gen: RoughGenerator;
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;

constructor(canvas: HTMLCanvasElement, config?: Config) {
  this.canvas = canvas;
  this.ctx = this.canvas.getContext('2d')!;
  this.gen = new RoughGenerator(config);
}

draw(drawable: Drawable) {
  const sets = drawable.sets || [];
  const o = drawable.options || this.getDefaultOptions();
  const ctx = this.ctx;
  for (const drawing of sets) {
    switch (drawing.type) {
      case 'path':
        ctx.save();
        ctx.strokeStyle = o.stroke === 'none' ? 'transparent' : o.stroke;
        ctx.lineWidth = o.strokeWidth;
        if (o.strokeLineDash) {
          ctx.setLineDash(o.strokeLineDash);
        }
        if (o.strokeLineDashOffset) {
          ctx.lineDashOffset = o.strokeLineDashOffset;
        }
        this._drawToContext(ctx, drawing);
        ctx.restore();
        break;
      case 'fillPath':
        ctx.save();
        ctx.fillStyle = o.fill || '';
        const fillRule: CanvasFillRule = (drawable.shape === 'curve' || drawable.shape === 'polygon') ? 'evenodd' : 'nonzero';
        this._drawToContext(ctx, drawing, fillRule);
        ctx.restore();
        break;
      case 'fillSketch':
        this.fillSketch(ctx, drawing, o);
        break;
    }
  }
}

private fillSketch(ctx: CanvasRenderingContext2D, drawing: OpSet, o: ResolvedOptions) {
  let fweight = o.fillWeight;
  if (fweight < 0) {
    fweight = o.strokeWidth / 2;
  }
  ctx.save();
  if (o.fillLineDash) {
    ctx.setLineDash(o.fillLineDash);
  }
  if (o.fillLineDashOffset) {
    ctx.lineDashOffset = o.fillLineDashOffset;
  }
  ctx.strokeStyle = o.fill || '';
  ctx.lineWidth = fweight;
  this._drawToContext(ctx, drawing);
  ctx.restore();
}

private _drawToContext(ctx: CanvasRenderingContext2D, drawing: OpSet, rule: CanvasFillRule = 'nonzero') {
  ctx.beginPath();
  for (const item of drawing.ops) {
    const data = item.data;
    switch (item.op) {
      case 'move':
        ctx.moveTo(data[0], data[1]);
        break;
      case 'bcurveTo':
        ctx.bezierCurveTo(data[0], data[1], data[2], data[3], data[4], data[5]);
        break;
      case 'lineTo':
        ctx.lineTo(data[0], data[1]);
        break;
    }
  }
  if (drawing.type === 'fillPath') {
    ctx.fill(rule);
  } else {
    ctx.stroke();
  }
}

get generator(): RoughGenerator {
  return this.gen;
}

getDefaultOptions(): ResolvedOptions {
  return this.gen.defaultOptions;
}

line(x1: number, y1: number, x2: number, y2: number, options?: Options): Drawable {
  const d = this.gen.line(x1, y1, x2, y2, options);
  this.draw(d);
  return d;
}

rectangle(x: number, y: number, width: number, height: number, options?: Options): Drawable {
  const d = this.gen.rectangle(x, y, width, height, options);
  this.draw(d);
  return d;
}

ellipse(x: number, y: number, width: number, height: number, options?: Options): Drawable {
  const d = this.gen.ellipse(x, y, width, height, options);
  this.draw(d);
  return d;
}

circle(x: number, y: number, diameter: number, options?: Options): Drawable {
  const d = this.gen.circle(x, y, diameter, options);
  this.draw(d);
  return d;
}

linearPath(points: Point[], options?: Options): Drawable {
  const d = this.gen.linearPath(points, options);
  this.draw(d);
  return d;
}

polygon(points: Point[], options?: Options): Drawable {
  const d = this.gen.polygon(points, options);
  this.draw(d);
  return d;
}

arc(x: number, y: number, width: number, height: number, start: number, stop: number, closed: boolean = false, options?: Options): Drawable {
  const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
  this.draw(d);
  return d;
}

curve(points: Point[], options?: Options): Drawable {
  const d = this.gen.curve(points, options);
  this.draw(d);
  return d;
}

path(d: string, options?: Options): Drawable {
  const drawing = this.gen.path(d, options);
  this.draw(drawing);
  return drawing;
}



*/

class SketchySvgCanvas extends mxSvgCanvas2D {
  constructor(node, shape) {
    super(node);
    this.shape = shape;
    this.passThrough = false;
    this.roughJS = new RoughJsAdaptor(this);
    // TODO is this needed? from draw.io
    // Avoids "spikes" in the output
    // this.canvas.setLineJoin('round');
    // this.canvas.setLineCap('round');
  }
  // TODO rename toRoughJsStyle
  // TODO rename arguments?
  getStyle(stroke, fill) {
    // TODO seed to ensure that there is no randomness for a given cell: same rendering after translation, scale or across reload
    //  if we want to introduce this feature, let's defined a style entry to make this configurable 'roughjsSeedActivated'
    // See https://roughjs.com/posts/release-4.0/
    // implementation from draw.io based on the cell ID
    // // Random seed created from cell ID
    // var seed = 1;
    //
    // if (this.shape.state != null)
    // {
    //   var str = this.shape.state.cell.id;
    //
    //   if (str != null)
    //   {
    //     for (var i = 0; i < str.length; i++)
    //     {
    //       seed = ((seed << 5) - seed + str.charCodeAt(i)) << 0;
    //     }
    //   }
    // }
    const style = { strokeWidth: this.state.strokeWidth };
    // TODO rename into roughDefaultOptions
    const defs = this.roughJS.getDefaultOptions();
    if (stroke) {
      style.stroke = this.state.strokeColor === 'none' ? 'transparent' : this.state.strokeColor;
    } else {
      delete style.stroke;
    }
    let gradient = null;
    if (fill) {
      style.fill = this.state.fillColor === 'none' ? '' : this.state.fillColor;
      gradient = this.state.gradientColor === 'none' ? null : this.state.gradientColor;
    }
    // Applies cell style
    style['bowing'] = mxUtils.getValue(this.shape.style, 'bowing', defs['bowing']);
    style['hachureAngle'] = mxUtils.getValue(this.shape.style, 'hachureAngle', defs['hachureAngle']);
    style['curveFitting'] = mxUtils.getValue(this.shape.style, 'curveFitting', defs['curveFitting']);
    style['roughness'] = mxUtils.getValue(this.shape.style, 'roughness', defs['roughness']);
    style['simplification'] = mxUtils.getValue(this.shape.style, 'simplification', defs['simplification']);
    style['disableMultiStroke'] = mxUtils.getValue(this.shape.style, 'disableMultiStroke', defs['disableMultiStroke']);
    style['disableMultiStrokeFill'] = mxUtils.getValue(
      this.shape.style,
      'disableMultiStrokeFill',
      defs['disableMultiStrokeFill']
    );
    const hachureGap = mxUtils.getValue(this.shape.style, 'hachureGap', -1);
    style['hachureGap'] = hachureGap == 'auto' ? -1 : hachureGap;
    style['dashGap'] = mxUtils.getValue(this.shape.style, 'dashGap', hachureGap);
    style['dashOffset'] = mxUtils.getValue(this.shape.style, 'dashOffset', hachureGap);
    style['zigzagOffset'] = mxUtils.getValue(this.shape.style, 'zigzagOffset', hachureGap);
    const fillWeight = mxUtils.getValue(this.shape.style, 'fillWeight', -1);
    style['fillWeight'] = fillWeight == 'auto' ? -1 : fillWeight;
    let fillStyle = mxUtils.getValue(this.shape.style, 'fillStyle', 'auto');
    if (fillStyle == 'auto') {
      // defaultPageBackgroundColor is draw.io specific
      fillStyle = style.fill != null && gradient != null ? 'solid' : defs['fillStyle'];
    }
    style['fillStyle'] = fillStyle;
    return style;
  }
  begin() {
    if (this.passThrough) {
      super.begin();
    } else {
      this.path = [];
      // TODO in super, lastX and lastY set to 0: no changes when adding that
      // this.lastX = 0;
      // this.lastY = 0;
    }
  }
  _addOp(actions) {
    if (this.path != null) {
      // console.error('@@custom addOp - not null path');
      this.path.push(actions[0]);
      if (actions.length > 2) {
        const s = this.state;
        for (let i = 2; i < actions.length; i += 2) {
          this.lastX = actions[i - 1];
          this.lastY = actions[i];
          // from the mxAbstractCanvas2D.prototype.addOp implementation
          if (this.passThrough) {
            // manage translation and scaling
            this.path.push(String(this.format(String((this.lastX + s.dx) * s.scale))));
            this.path.push(String(this.format(String((this.lastY + s.dy) * s.scale))));
          } else {
            this.path.push(String(this.format(String(this.lastX))));
            this.path.push(String(this.format(String(this.lastY))));
          }
        }
      }
    }
  }
  lineTo(x, y) {
    if (this.passThrough) {
      // console.error('#### custom lineTo - passThrough');
      super.lineTo(x, y);
    } else {
      // console.error('#### custom lineTo - manual');
      this._addOp([this.lineOp, x, y]);
      // console.error(`#### custom lineTo - after - this.lastX: ${this.lastX} / this.lastY: ${this.lastY} / x: ${x} / y: ${y}`);
      // in original draw.io implem but managed by _addOp
      // this.lastX = x;
      // this.lastY = y;
      // console.error(`#### custom lineTo - after extra 'last coordinates' set`);
    }
  }
  moveTo(x, y) {
    if (this.passThrough) {
      // console.error('#### custom moveTo - passThrough');
      super.moveTo(x, y);
    } else {
      // console.error('#### custom moveTo - manual');
      this._addOp([this.moveOp, x, y]);
      // this.lastX = x;
      // this.lastY = y;
      // TODO is this really needed, never called later + don't exist in super class
      // this.firstX = x;
      // this.firstY = y;
    }
  }
  close() {
    if (this.passThrough) {
      // console.error('#### custom close - passThrough');
      super.close();
    } else {
      // console.error('#### custom close - manual');
      // super.close();
      this._addOp([this.closeOp]);
    }
  }
  //
  quadTo(x1, y1, x2, y2) {
    if (this.passThrough) {
      super.quadTo(x1, y1, x2, y2);
    } else {
      this._addOp([this.quadOp, x1, y1, x2, y2]);
      // super.quadTo(x1, y1, x2, y2);
      // this.lastX = x2;
      // this.lastY = y2;
    }
  }
  curveTo(x1, y1, x2, y2, x3, y3) {
    if (this.passThrough) {
      super.curveTo(x1, y1, x2, y2, x3, y3);
    } else {
      this._addOp([this.curveOp, x1, y1, x2, y2, x3, y3]);
      // super.curveTo(x1, y1, x2, y2, x3, y3);
      // this.lastX = x3;
      // this.lastY = y3;
    }
  }
  rect(x, y, w, h) {
    if (this.passThrough) {
      // console.error('#### custom rect - passThrough');
      super.rect(x, y, w, h);
    } else {
      // console.error('#### custom rect - manual');
      this.path = [];
      this.nextShape = this.roughJS.generator.rectangle(x, y, w, h, this.getStyle(true, true));
    }
  }
  ellipse(x, y, w, h) {
    if (this.passThrough) {
      // console.error('#### custom ellipse - passThrough');
      super.ellipse(x, y, w, h);
    } else {
      // console.error('#### custom ellipse - manual');
      this.path = [];
      this.nextShape = this.roughJS.generator.ellipse(x + w / 2, y + h / 2, w, h, this.getStyle(true, true));
    }
  }
  // Redefine the implementation because
  // the original one applies scaling on dx/dy and scaling must be applied after roughjs processing
  // we need that the rounded parts goes through roughjs
  roundrect(x, y, w, h, dx, dy) {
    if (this.passThrough) {
      // console.error('#### custom RoundRect - passThrough');
      super.roundrect(x, y, w, h, dx, dy);
    } else {
      // console.error('#### custom RoundRect - manual');
      this.begin();
      this.moveTo(x + dx, y);
      this.lineTo(x + w - dx, y);
      this.quadTo(x + w, y, x + w, y + dy);
      this.lineTo(x + w, y + h - dy);
      this.quadTo(x + w, y + h, x + w - dx, y + h);
      this.lineTo(x + dx, y + h);
      this.quadTo(x, y + h, x, y + h - dy);
      this.lineTo(x, y + dy);
      this.quadTo(x, y, x + dx, y);
    }
  }
  drawPath(style) {
    if (this.path.length > 0) {
      this.passThrough = true;
      try {
        this.roughJS.path(this.path.join(' '), style);
      } catch (e) {
        // ignore
      }
      this.passThrough = false;
    } else if (this.nextShape != null) {
      for (const key in style) {
        // here we know this is a valid operation
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        this.nextShape.options[key] = style[key];
      }
      if (style['stroke'] == null) {
        delete this.nextShape.options['stroke'];
      }
      if (!style.fill) {
        delete this.nextShape.options['fill'];
      }
      this.passThrough = true;
      this.roughJS.draw(this.nextShape);
      this.passThrough = false;
    }
  }
  stroke() {
    if (this.passThrough) {
      super.stroke();
    } else {
      this.drawPath(this.getStyle(true, false));
    }
  }
  fill() {
    if (this.passThrough) {
      super.fill();
    } else {
      this.drawPath(this.getStyle(false, true));
    }
  }
  fillAndStroke() {
    if (this.passThrough) {
      super.fillAndStroke();
    } else {
      this.drawPath(this.getStyle(true, true));
    }
  }
}

function configureMxGraphSketchSupport() {
  configureMxShapePrototype();
  // TODO understand why we would need this (comes from draw.io)
  overrideMxShapePaint(false);
  overrideMxRectangleShapePaintBackground(false);
  overrideMxRectangleShapePaintForeground(false);
}
function configureMxShapePrototype() {
  // adapted from  https://github.com/jgraph/mxgraph2/blob/mxgraph-4_2_0/javascript/src/js/shape/mxShape.js#L625
  mxShape.prototype.createSvgCanvas = function () {
    const canvas = newSvgCanvas(this.node, this);
    canvas.strokeTolerance = this.pointerEvents ? this.svgStrokeTolerance : 0;
    canvas.pointerEventsValue = this.svgPointerEvents;
    const off = this.getSvgScreenOffset();
    if (off != 0) {
      this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')');
    } else {
      this.node.removeAttribute('transform');
    }
    canvas.minStrokeWidth = this.minSvgStrokeWidth;
    if (!this.antiAlias) {
      // Rounds all numbers in the SVG output to integers
      canvas.format = function (value) {
        return Math.round(parseFloat(value));
      };
    }
    return canvas;
  };
}
function isCustomizationRequiredForSketchStyle(shape) {
  if (shape.outline) return false;
  return mxUtils.getValue(shape.style, 'sketch', false) == true;
}
function newSvgCanvas(node, shape) {
  if (isCustomizationRequiredForSketchStyle(shape)) {
    return new SketchySvgCanvas(node, shape);
  }
  return new mxSvgCanvas2D(node, false);
}
function overrideMxShapePaint(activate) {
  if (!activate) return;
  // TODO see if we need this (already adapted to TS and our code)
  // TODO only when sketch is on!!!!
  // Overrides for event handling on transparent background for sketch style
  const shapePaint = mxShape.prototype.paint;
  mxShape.prototype.paint = function (c) {
    let fillStyle = null;
    let events = true;
    if (this.style != null) {
      events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';
      fillStyle = mxUtils.getValue(this.style, 'fillStyle', 'auto');
      if (this.state != null && fillStyle == 'auto') {
        //var bg = this.state.view.graph.defaultPageBackgroundColor;
        // if (this.fill != null && (this.gradient != null || (bg != null && this.fill.toLowerCase() == bg.toLowerCase()))) {
        if (this.style.fill != null && this.gradient != null) {
          fillStyle = 'solid';
        }
      }
    }
    if (
      events &&
      c instanceof SketchySvgCanvas &&
      !this.outline &&
      (this.fill == null || this.fill == mxConstants.NONE || fillStyle != 'solid')
    ) {
      // if (events && c.handJiggle != null && c.handJiggle.constructor == RoughCanvas &&
      //   !this.outline && (this.fill == null || this.fill == mxConstants.NONE ||
      //     fillStyle != 'solid'))
      // Save needed for possible transforms applied during paint
      c.save();
      const fill = this.fill;
      const stroke = this.stroke;
      this.fill = null;
      this.stroke = null;
      c.passThrough = true;
      shapePaint.apply(this, [c]);
      c.passThrough = false;
      this.fill = fill;
      this.stroke = stroke;
      c.restore();
    }
    shapePaint.apply(this, [c]);
  };
}
function overrideMxRectangleShapePaintBackground(activate) {
  if (!activate) return;
  // from Draw.io
  // TODO add link to draw.io code
  // Overrides to avoid call to rect
  const mxRectangleShapePaintBackground0 = mxRectangleShape.prototype.paintBackground;
  mxRectangleShape.prototype.paintBackground = function (c, x, y, w, h) {
    if (!(c instanceof SketchySvgCanvas)) {
      mxRectangleShapePaintBackground0.apply(this, [c, x, y, w, h]);
    } else {
      let events = true;
      if (this.style != null) {
        events = mxUtils.getValue(this.style, mxConstants.STYLE_POINTER_EVENTS, '1') == '1';
      }
      if (
        events ||
        (this.fill != null && this.fill != mxConstants.NONE) ||
        (this.stroke != null && this.stroke != mxConstants.NONE)
      ) {
        if (!events && (this.fill == null || this.fill == mxConstants.NONE)) {
          c.pointerEvents = false;
        }
        c.begin();
        if (this.isRounded) {
          let r = 0;
          if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') {
            r = Math.min(
              w / 2,
              Math.min(h / 2, mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2)
            );
          } else {
            const f =
              mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) /
              100;
            r = Math.min(w * f, h * f);
          }
          c.moveTo(x + r, y);
          c.lineTo(x + w - r, y);
          c.quadTo(x + w, y, x + w, y + r);
          c.lineTo(x + w, y + h - r);
          c.quadTo(x + w, y + h, x + w - r, y + h);
          c.lineTo(x + r, y + h);
          c.quadTo(x, y + h, x, y + h - r);
          c.lineTo(x, y + r);
          c.quadTo(x, y, x + r, y);
        } else {
          c.moveTo(x, y);
          c.lineTo(x + w, y);
          c.lineTo(x + w, y + h);
          c.lineTo(x, y + h);
          c.lineTo(x, y);
        }
        // LATER: Check if close is needed here
        c.close();
        c.end();
        c.fillAndStroke();
      }
    }
  };
}
function overrideMxRectangleShapePaintForeground(activate) {
  if (!activate) return;
  // from draw.io: Disables glass effect with hand jiggle.
  const mxRectangleShapePaintForeground0 = mxRectangleShape.prototype.paintForeground;
  mxRectangleShape.prototype.paintForeground = function (c, x, y, w, h) {
    if (!(c instanceof SketchySvgCanvas)) {
      mxRectangleShapePaintForeground0.apply(this, [c, x, y, w, h]);
    }
  };
}

configureMxGraphSketchSupport();
// E 草图支持
