").append( jQuery.parseHTML( responseText ) ).find( selector ) :
-
- // Otherwise use the full result
- responseText );
-
- }).complete( callback && function( jqXHR, status ) {
- self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
- });
- }
-
- return this;
-};
-
-
-
-
-// Attach a bunch of functions for handling common AJAX events
-jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) {
- jQuery.fn[ type ] = function( fn ) {
- return this.on( type, fn );
- };
-});
-
-
-
-
-jQuery.expr.filters.animated = function( elem ) {
- return jQuery.grep(jQuery.timers, function( fn ) {
- return elem === fn.elem;
- }).length;
-};
-
-
-
-
-
-var docElem = window.document.documentElement;
-
-/**
- * Gets a window from an element
- */
-function getWindow( elem ) {
- return jQuery.isWindow( elem ) ?
- elem :
- elem.nodeType === 9 ?
- elem.defaultView || elem.parentWindow :
- false;
-}
-
-jQuery.offset = {
- setOffset: function( elem, options, i ) {
- var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
- position = jQuery.css( elem, "position" ),
- curElem = jQuery( elem ),
- props = {};
-
- // set position first, in-case top/left are set even on static elem
- if ( position === "static" ) {
- elem.style.position = "relative";
- }
-
- curOffset = curElem.offset();
- curCSSTop = jQuery.css( elem, "top" );
- curCSSLeft = jQuery.css( elem, "left" );
- calculatePosition = ( position === "absolute" || position === "fixed" ) &&
- jQuery.inArray("auto", [ curCSSTop, curCSSLeft ] ) > -1;
-
- // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
- if ( calculatePosition ) {
- curPosition = curElem.position();
- curTop = curPosition.top;
- curLeft = curPosition.left;
- } else {
- curTop = parseFloat( curCSSTop ) || 0;
- curLeft = parseFloat( curCSSLeft ) || 0;
- }
-
- if ( jQuery.isFunction( options ) ) {
- options = options.call( elem, i, curOffset );
- }
-
- if ( options.top != null ) {
- props.top = ( options.top - curOffset.top ) + curTop;
- }
- if ( options.left != null ) {
- props.left = ( options.left - curOffset.left ) + curLeft;
- }
-
- if ( "using" in options ) {
- options.using.call( elem, props );
- } else {
- curElem.css( props );
- }
- }
-};
-
-jQuery.fn.extend({
- offset: function( options ) {
- if ( arguments.length ) {
- return options === undefined ?
- this :
- this.each(function( i ) {
- jQuery.offset.setOffset( this, options, i );
- });
- }
-
- var docElem, win,
- box = { top: 0, left: 0 },
- elem = this[ 0 ],
- doc = elem && elem.ownerDocument;
-
- if ( !doc ) {
- return;
- }
-
- docElem = doc.documentElement;
-
- // Make sure it's not a disconnected DOM node
- if ( !jQuery.contains( docElem, elem ) ) {
- return box;
- }
-
- // If we don't have gBCR, just use 0,0 rather than error
- // BlackBerry 5, iOS 3 (original iPhone)
- if ( typeof elem.getBoundingClientRect !== strundefined ) {
- box = elem.getBoundingClientRect();
- }
- win = getWindow( doc );
- return {
- top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ),
- left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
- };
- },
-
- position: function() {
- if ( !this[ 0 ] ) {
- return;
- }
-
- var offsetParent, offset,
- parentOffset = { top: 0, left: 0 },
- elem = this[ 0 ];
-
- // fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent
- if ( jQuery.css( elem, "position" ) === "fixed" ) {
- // we assume that getBoundingClientRect is available when computed position is fixed
- offset = elem.getBoundingClientRect();
- } else {
- // Get *real* offsetParent
- offsetParent = this.offsetParent();
-
- // Get correct offsets
- offset = this.offset();
- if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
- parentOffset = offsetParent.offset();
- }
-
- // Add offsetParent borders
- parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
- parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
- }
-
- // Subtract parent offsets and element margins
- // note: when an element has margin: auto the offsetLeft and marginLeft
- // are the same in Safari causing offset.left to incorrectly be 0
- return {
- top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
- left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
- };
- },
-
- offsetParent: function() {
- return this.map(function() {
- var offsetParent = this.offsetParent || docElem;
-
- while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position" ) === "static" ) ) {
- offsetParent = offsetParent.offsetParent;
- }
- return offsetParent || docElem;
- });
- }
-});
-
-// Create scrollLeft and scrollTop methods
-jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
- var top = /Y/.test( prop );
-
- jQuery.fn[ method ] = function( val ) {
- return access( this, function( elem, method, val ) {
- var win = getWindow( elem );
-
- if ( val === undefined ) {
- return win ? (prop in win) ? win[ prop ] :
- win.document.documentElement[ method ] :
- elem[ method ];
- }
-
- if ( win ) {
- win.scrollTo(
- !top ? val : jQuery( win ).scrollLeft(),
- top ? val : jQuery( win ).scrollTop()
- );
-
- } else {
- elem[ method ] = val;
- }
- }, method, val, arguments.length, null );
- };
-});
-
-// Add the top/left cssHooks using jQuery.fn.position
-// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
-// getComputedStyle returns percent when specified for top/left/bottom/right
-// rather than make the css module depend on the offset module, we just check for it here
-jQuery.each( [ "top", "left" ], function( i, prop ) {
- jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
- function( elem, computed ) {
- if ( computed ) {
- computed = curCSS( elem, prop );
- // if curCSS returns percentage, fallback to offset
- return rnumnonpx.test( computed ) ?
- jQuery( elem ).position()[ prop ] + "px" :
- computed;
- }
- }
- );
-});
-
-
-// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
-jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
- jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
- // margin is only for outerHeight, outerWidth
- jQuery.fn[ funcName ] = function( margin, value ) {
- var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
- extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
-
- return access( this, function( elem, type, value ) {
- var doc;
-
- if ( jQuery.isWindow( elem ) ) {
- // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
- // isn't a whole lot we can do. See pull request at this URL for discussion:
- // https://github.com/jquery/jquery/pull/764
- return elem.document.documentElement[ "client" + name ];
- }
-
- // Get document width or height
- if ( elem.nodeType === 9 ) {
- doc = elem.documentElement;
-
- // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
- // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
- return Math.max(
- elem.body[ "scroll" + name ], doc[ "scroll" + name ],
- elem.body[ "offset" + name ], doc[ "offset" + name ],
- doc[ "client" + name ]
- );
- }
-
- return value === undefined ?
- // Get width or height on the element, requesting but not forcing parseFloat
- jQuery.css( elem, type, extra ) :
-
- // Set width or height on the element
- jQuery.style( elem, type, value, extra );
- }, type, chainable ? margin : undefined, chainable, null );
- };
- });
-});
-
-
-// The number of elements contained in the matched element set
-jQuery.fn.size = function() {
- return this.length;
-};
-
-jQuery.fn.andSelf = jQuery.fn.addBack;
-
-
-
-
-// Register as a named AMD module, since jQuery can be concatenated with other
-// files that may use define, but not via a proper concatenation script that
-// understands anonymous AMD modules. A named AMD is safest and most robust
-// way to register. Lowercase jquery is used because AMD module names are
-// derived from file names, and jQuery is normally delivered in a lowercase
-// file name. Do this after creating the global so that if an AMD module wants
-// to call noConflict to hide this version of jQuery, it will work.
-
-// Note that for maximum portability, libraries that are not jQuery should
-// declare themselves as anonymous modules, and avoid setting a global if an
-// AMD loader is present. jQuery is a special case. For more information, see
-// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
-
-if ( typeof define === "function" && define.amd ) {
- define( "jquery", [], function() {
- return jQuery;
- });
-}
-
-
-
-
-var
- // Map over jQuery in case of overwrite
- _jQuery = window.jQuery,
-
- // Map over the $ in case of overwrite
- _$ = window.$;
-
-jQuery.noConflict = function( deep ) {
- if ( window.$ === jQuery ) {
- window.$ = _$;
- }
-
- if ( deep && window.jQuery === jQuery ) {
- window.jQuery = _jQuery;
- }
-
- return jQuery;
-};
-
-// Expose jQuery and $ identifiers, even in
-// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
-// and CommonJS for browser emulators (#13566)
-if ( typeof noGlobal === strundefined ) {
- window.jQuery = window.$ = jQuery;
-}
-
-
-
-
-return jQuery;
-
-}));
diff --git a/MyJade/myjade.js b/MyJade/myjade.js
deleted file mode 100644
index 284ac68..0000000
--- a/MyJade/myjade.js
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-$(document).ready(function () {
- $('.jade').each(function(index, div) {
-
- // var j = new jade_defs.jade();
- // jade_defs.services(j);
- // j.setup(div);
- });
-});
-
-class Jade{
- constructor(){
-
- this.top_level = $( '
' +
- '
'+
- '
');
-
- }
-
-
-}
\ No newline at end of file
diff --git a/MyJade/schematic.js b/MyJade/schematic.js
deleted file mode 100644
index e69de29..0000000
diff --git a/README.md b/README.md
index deb6adc..9eb182c 100644
--- a/README.md
+++ b/README.md
@@ -6,9 +6,9 @@
## 项目框架
-### MyJade
+### Schematic
-能够绘制电路原理图的web前端代码,主要参考MIT的Jade项目。
+能够绘制电路原理图的web前端代码,主要采用mxGraph代码构建。
### Server
@@ -42,6 +42,12 @@
## 更新日志
+* 2021年1月26日,重大更新!!!增加了前端电路绘制功能demo
+
+ * 改变技术方案,放弃采用MIT的Jade电路绘制前端,改为使用mxGraph设计电路绘制功能
+ * 当前已经可以实现绘制简单的电路原理图,并将拓扑关系提取到xml格式文件中,后续可设计程序将xml转换为spice文件
+ * 当前已经增加了几个简单的N_Mosfet P_Mosfet Vdd Gnd Resistor等元件的图表
+ * 但是,还有很多细节需要继续填充
* 2021年1月26日,修复了一些小bug,增加了一些配置信息
* 增加了README的内容
diff --git a/Schematic/mxClient.js b/Schematic/mxClient.js
new file mode 100644
index 0000000..a8b42fb
--- /dev/null
+++ b/Schematic/mxClient.js
@@ -0,0 +1,91957 @@
+/**
+ * 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
+ * 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
+ * or 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('');
+ }
+ 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 .
+ */
+ 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('');
+ }
+};
+
+/**
+ * Variable: mxLoadResources
+ *
+ * Optional global config variable to toggle loading of the two resource files
+ * in and . Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient. If this is false, you can use
+ * with its callback to load the default bundles asynchronously.
+ *
+ * (code)
+ *
+ *
+ * (end)
+ */
+if (typeof(mxLoadResources) == 'undefined')
+{
+ 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)
+ *
+ *
+ * (end)
+ */
+if (typeof(mxForceIncludes) == 'undefined')
+{
+ 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)
+ *
+ *
+ * (end)
+ */
+if (typeof(mxResourceExtension) == 'undefined')
+{
+ 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)
+ *
+ *
+ * (end)
+ */
+if (typeof(mxLoadStylesheets) == 'undefined')
+{
+ 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)
+ *
+ *
+ * (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 = '.';
+}
+
+/**
+ * Variable: imageBasePath
+ *
+ * Basepath for all images URLs in the core without trailing slash. Default is
+ * + '/images'. Set mxImageBasePath prior to loading the
+ * mxClient library as follows to override this setting:
+ *
+ * (code)
+ *
+ *
+ * (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 for handling identifiers
+ * with and without a dash.
+ *
+ * Set mxLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ *
+ *
+ * (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.
+ * , ,
+ * , ,
+ * , , ,
+ * , ,
+ * , ,
+ * , ,
+ * , ,
+ * and
+ * .
+ */
+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)
+ *
+ *
+ * (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
+ * .
+ *
+ * (code)
+ *
+ *
+ * (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)}';
+ }
+
+ if (mxLoadStylesheets)
+ {
+ mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css');
+ }
+ }
+}
+
+/**
+ * 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 and should be visible in the
+ * console. Default is false.
+ */
+ TRACE: false,
+
+ /**
+ * Variable: DEBUG
+ *
+ * Specifies if the output for should be visible in the console.
+ * Default is true.
+ */
+ DEBUG: true,
+
+ /**
+ * Variable: WARN
+ *
+ * Specifies if the output for 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 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 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 is true and computes the difference
+ * between the current time and t0 in milliseconds.
+ * See 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 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 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');
+ }
+
+};
+/**
+ * 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
+ * .
+ *
+ * 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
+ * mxObjectId
.
+ */
+ 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];
+ }
+ }
+
+};
+/**
+ * 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 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]);
+ }
+};
+/**
+ * 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 . Lines without
+ * equal signs in the properties files are ignored.
+ *
+ * Resource files may either be added programmatically using
+ * or via a resource tag in the UI section of the
+ * editor configuration file, eg:
+ *
+ * (code)
+ *
+ *
+ *
+ * (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 . The placeholder {1} maps to the first
+ * element in the array (at index 0).
+ *
+ * See 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" (ö).
+ *
+ * See 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 to false
+ * before loading mxClient.js and use instead.
+ *
+ * Variable: resources
+ *
+ * Object that maps from keys to values.
+ */
+ resources: {},
+
+ /**
+ * Variable: extension
+ *
+ * Specifies the extension used for language files. Default is .
+ */
+ 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 .
+ *
+ * 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 + or null if
+ * 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 + or null if
+ * is false or lan equals .
+ *
+ * If is not null and contains
+ * a dash, then this method checks if 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 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 is false.
+ *
+ * Parameters:
+ *
+ * callback - Callback function for asynchronous loading.
+ */
+ loadResources: function(callback)
+ {
+ mxResources.add(mxClient.basePath+'/resources/editor', null, function()
+ {
+ mxResources.add(mxClient.basePath+'/resources/graph', null, callback);
+ });
+ }
+
+};
+/**
+ * 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 and 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.prototype.clone = function()
+{
+ // Handles subclasses as well
+ return mxUtils.clone(this);
+};
+/**
+ * Copyright (c) 2006-2015, JGraph Ltd
+ * Copyright (c) 2006-2015, Gaudenz Alder
+ */
+/**
+ * Class: mxRectangle
+ *
+ * Extends 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 .
+ */
+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 which is a copy of the given rectangle.
+ */
+mxRectangle.fromRectangle = function(rect)
+{
+ return new mxRectangle(rect.x, rect.y, rect.width, rect.height);
+};
+/**
+ * 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: .
+ *
+ * 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 - 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 - that contains the cells.
+ * cell - 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 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);
+ }
+ }
+ }
+
+};
+/**
+ * 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 ,
+ * , and 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
+ * .
+ */
+ 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(
+ * ''+
+ * ''+
+ * ''+
+ * ''+
+ * '');
+ * (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');
+ };
+ }
+ else // IE<=9
+ {
+ 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,'&'); // 38 26
+ s = s.replace(/"/g,'"'); // 34 22
+ s = s.replace(/\'/g,'''); // 39 27
+ s = s.replace(//g,'>'); // 62 3E
+
+ if (newline == null || newline)
+ {
+ s = s.replace(/\n/g, '
');
+ }
+
+ 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
if
+ * no linefeed is defined.
+ *
+ * Parameters:
+ *
+ * node - DOM node to return the XML for.
+ * linefeed - Optional string that linefeeds are converted into. Default is
+ *
+ */
+ 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 || '
';
+ 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 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 + '' + 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 + '' + 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() == '
')))
+ {
+ 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 - 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 - 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 .
+ */
+ 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 .
+ * Throws an exception if the file cannot be loaded. See 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 in use. Both
+ * functions take the as the only parameter. See
+ * 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 .
+ * 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
+ * in use. Both functions take the 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
+ * and returns the .
+ * 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 , 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 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. 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 to be compared.
+ * b - Array of 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 - to be rotated.
+ * angle - Number that represents the angle (in degrees).
+ * cx - Optional 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 - that represents the terminal.
+ * edge - 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 - that represents the rectangle.
+ * p1 - that represents the first point of the segment.
+ * p2 - 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 - 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 - to be checked for intersection.
+ * b - 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 -
+ * 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 . 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)
+ {
+ var offsetLeft = 0;
+ var offsetTop = 0;
+
+ // 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 .
+ *
+ * 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 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 .
+ *
+ * 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
+ * .
+ *
+ * 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 .
+ *
+ * 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 . 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 . This is for backwards compatibility and
+ * will be removed later.
+ */
+ cascadeOpacity: function(graph, cell, opacity)
+ {
+ mxEffects.cascadeOpacity.apply(this, arguments);
+ },
+
+ /**
+ * Function: fadeOut
+ *
+ * See . 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 - to execute the transaction in.
+ * cells - Array of 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 - that contains the cells.
+ * cells - Array of 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 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 with the size (width and height in pixels) of
+ * the given string. The string may contain HTML markup. Newlines should be
+ * converted to
before calling this method. The caller is responsible
+ * for sanitizing the HTML markup.
+ *
+ * Example:
+ *
+ * (code)
+ * var label = graph.getLabel(cell).replace(/\n/g, "
");
+ * 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
+ * .
+ * fontFamily - String that specifies the name of the font family. Default
+ * is .
+ * 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 for an example.
+ *
+ * Parameters:
+ *
+ * pageCount - Specifies the number of pages in the print output.
+ * graph - that should be printed.
+ * pageFormat - Optional that specifies the page format.
+ * Default is .
+ * 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 - 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('');
+ }
+
+ 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 = '';
+
+ var base = document.getElementsByTagName('base');
+
+ for (var i = 0; i < base.length; i++)
+ {
+ html += base[i].outerHTML;
+ }
+
+ html += '';
+
+ // Copies the contents of the graph container
+ html += '';
+ html += graph.container.innerHTML;
+ html += '
';
+
+ doc.writeln(html);
+ doc.close();
+ }
+ else
+ {
+ doc.writeln('');
+
+ 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('');
+ 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 - 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 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,'
').replace(/ /g, ' ');
+
+ 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(''+mxUtils.htmlEntities(content)+'
').replace(/ /g, ' ');
+ 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 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 .
+ *
+ * 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')); //
+ div.appendChild(document.createTextNode('\u00a0')); //
+ div.appendChild(document.createTextNode('\u00a0')); //
+ 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 . If
+ * 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 - that acts as the drop target or a function that takes a
+ * mouse event and returns the current .
+ * 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;
+ }
+
+};
+/**
+ * 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 .
+ */
+ OUTLINE_HIGHLIGHT_COLOR: '#00FF00',
+
+ /**
+ * Variable: OUTLINE_HIGHLIGHT_COLOR
+ *
+ * Defines the strokewidth to be used for shape outlines.
+ * Default is 5. This is used in .
+ */
+ 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 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 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 . Alternatively, the constants in this
+ * class that start with "PERIMETER_" may be used to access
+ * perimeter styles in . 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 .
+ * 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 . 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 , 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 . 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 . 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
+ * , , and
+ * . Default is . Generally, and by
+ * default in mxGraph, gradient painting is done from the value of
+ * to the value of . Taking the
+ * example of , this means color at the
+ * bottom of paint pattern and 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
+ * 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 ,
+ * and . This value defines how the lines of
+ * the label are horizontally aligned. mean label text lines
+ * are aligned to left of the label bounds, to the right of
+ * the label bounds and 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
+ * . Value is "align".
+ */
+ STYLE_ALIGN: 'align',
+
+ /**
+ * Variable: STYLE_VERTICAL_ALIGN
+ *
+ * Defines the key for the verticalAlign style. Possible values are
+ * , and . This value defines how
+ * the lines of the label are vertically aligned. means the
+ * topmost label text line is aligned against the top of the label bounds,
+ * means the bottom-most label text line is aligned against
+ * the bottom of the label bounds and 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 . 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 , and . Default is
+ * . The label align defines the position of the label
+ * relative to the cell. means the entire label bounds is
+ * placed completely just to the left of the vertex, means
+ * adjust to the right and 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 .
+ * 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 , and . Default is
+ * . The label align defines the position of the label
+ * relative to the cell. means the entire label bounds is
+ * placed completely just on the top of the vertex, means
+ * adjust on the bottom and 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
+ * . 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
+ * . Default is 1. Value is "imageAspect".
+ */
+ STYLE_IMAGE_ASPECT: 'imageAspect',
+
+ /**
+ * Variable: STYLE_IMAGE_ALIGN
+ *
+ * Defines the key for the align style. Possible values are ,
+ * and . The value defines how any image in the
+ * vertex label is aligned horizontally within the label bounds of a
+ * shape. Value is "imageAlign".
+ */
+ STYLE_IMAGE_ALIGN: 'imageAlign',
+
+ /**
+ * Variable: STYLE_IMAGE_VERTICAL_ALIGN
+ *
+ * Defines the key for the verticalAlign style. Possible values are
+ * , and . The value defines how
+ * any image in the vertex label is aligned vertically within the label
+ * bounds of a 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 . 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 . 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
+ * . 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 . 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 .
+ * 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 .
+ * 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 .
+ * 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
+ * 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 . 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. ).
+ * Possible values are (default), ,
+ * and . 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 .
+ * 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 .
+ * See . 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 .) 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 .) 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 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 .) 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
+ * 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. ).
+ * Possible values are (default), ,
+ * and . 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
+ * and . Default is .
+ * 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 , and .
+ * 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 . This is normally combined with
+ * 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
+ * . 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 . 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 . 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
+ * . 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
+ * . 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
+ * . 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
+ * . 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
+ * . 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 . Value is "edgeStyle".
+ */
+ STYLE_EDGE: 'edgeStyle',
+
+ /**
+ * Variable: STYLE_JETTY_SIZE
+ *
+ * Defines the key for the jetty size in .
+ * 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 .
+ * Default is 10. Possible values are numeric values or "auto". This has
+ * precedence over . Value is "sourceJettySize".
+ */
+ STYLE_SOURCE_JETTY_SIZE: 'sourceJettySize',
+
+ /**
+ * Variable: targetJettySize
+ *
+ * Defines the key for the jetty size in .
+ * Default is 10. Possible values are numeric values or "auto". This has
+ * precedence over . 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