/** * 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.1.1. */ VERSION: '4.1.1', /** * 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: /constructor/i.test(window.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window['safari'] || (typeof safari !== 'undefined' && safari.pushNotification)), /** * 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: IOS_VERSION * * Returns the major version number for iOS devices or 0 if the * device is not an iOS device. */ IOS_VERSION: (function() { if ((/iP(hone|od|ad)/.test(navigator.platform))) { var v = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/); if (v != null && v.length > 0) { return parseInt(v[1]); } } return 0; })(), /** * 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(''); } }; /** * Detects desktop mode on iPad Pro which should block event handling like iOS 12. */ if (mxClient.IS_SF && mxClient.IS_TOUCH && !mxClient.IS_IOS) { mxClient.IOS_VERSION = 13; mxClient.IOS = true; } /** * 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; }; /** * 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.getTextContent(node); if (value.length > 0) { result.push(indent + mxUtils.htmlEntities(mxUtils.trim(value), false) + newline); } } else { result.push(indent + '<' + node.nodeName); // Creates the string with the node attributes // and converts all HTML entities in the values var attrs = node.attributes; if (attrs != null) { for (var i = 0; i < attrs.length; i++) { var val = mxUtils.htmlEntities(attrs[i].value); result.push(' ' + attrs[i].nodeName + '="' + val + '"'); } } // Recursively creates the XML string for each child // node and appends it here with an indentation var tmp = node.firstChild; if (tmp != null) { result.push('>' + newline); while (tmp != null) { result.push(mxUtils.getPrettyXml(tmp, tab, indent + tab, newline, ns)); tmp = tmp.nextSibling; } result.push(indent + '' + 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(''); } 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: intersects * * Returns true if the two rectangles intersect. * * Parameters: * * a - to be checked for intersection. * b - to be checked for intersection. */ 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
	 * 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',

	/**
	 * 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 . Value is "loopStyle". Default is
	 * .
	 */
	STYLE_LOOP: 'loopStyle',

	/**
	 * Variable: STYLE_ORTHOGONAL_LOOP
	 * 
	 * Defines the key for the orthogonal loop style. Possible values are 0 and
	 * 1. Default is 0. Value is "orthogonalLoop". Use this style to specify
	 * if loops with no waypoints and defined anchor points should be routed
	 * using  or not routed.
	 */
	STYLE_ORTHOGONAL_LOOP: 'orthogonalLoop',

	/**
	 * Variable: STYLE_ROUTING_CENTER_X
	 * 
	 * Defines the key for the horizontal routing center. Possible values are
	 * between -0.5 and 0.5. This is the relative offset from the center used
	 * for connecting edges. The type of this value is numeric. Value is
	 * "routingCenterX".
	 */
	STYLE_ROUTING_CENTER_X: 'routingCenterX',

	/**
	 * Variable: STYLE_ROUTING_CENTER_Y
	 * 
	 * Defines the key for the vertical routing center. Possible values are
	 * between -0.5 and 0.5. This is the relative offset from the center used
	 * for connecting edges. The type of this value is numeric. Value is
	 * "routingCenterY".
	 */
	STYLE_ROUTING_CENTER_Y: 'routingCenterY',

	/**
	 * Variable: FONT_BOLD
	 * 
	 * Constant for bold fonts. Default is 1.
	 */
	FONT_BOLD: 1,

	/**
	 * Variable: FONT_ITALIC
	 * 
	 * Constant for italic fonts. Default is 2.
	 */
	FONT_ITALIC: 2,

	/**
	 * Variable: FONT_UNDERLINE
	 * 
	 * Constant for underlined fonts. Default is 4.
	 */
	FONT_UNDERLINE: 4,

	/**
	 * Variable: FONT_STRIKETHROUGH
	 * 
	 * Constant for strikthrough fonts. Default is 8.
	 */
	FONT_STRIKETHROUGH: 8,
	
	/**
	 * Variable: SHAPE_RECTANGLE
	 * 
	 * Name under which  is registered in .
	 * Default is rectangle.
	 */
	SHAPE_RECTANGLE: 'rectangle',

	/**
	 * Variable: SHAPE_ELLIPSE
	 * 
	 * Name under which  is registered in .
	 * Default is ellipse.
	 */
	SHAPE_ELLIPSE: 'ellipse',

	/**
	 * Variable: SHAPE_DOUBLE_ELLIPSE
	 * 
	 * Name under which  is registered in .
	 * Default is doubleEllipse.
	 */
	SHAPE_DOUBLE_ELLIPSE: 'doubleEllipse',

	/**
	 * Variable: SHAPE_RHOMBUS
	 * 
	 * Name under which  is registered in .
	 * Default is rhombus.
	 */
	SHAPE_RHOMBUS: 'rhombus',

	/**
	 * Variable: SHAPE_LINE
	 * 
	 * Name under which  is registered in .
	 * Default is line.
	 */
	SHAPE_LINE: 'line',

	/**
	 * Variable: SHAPE_IMAGE
	 * 
	 * Name under which  is registered in .
	 * Default is image.
	 */
	SHAPE_IMAGE: 'image',
	
	/**
	 * Variable: SHAPE_ARROW
	 * 
	 * Name under which  is registered in .
	 * Default is arrow.
	 */
	SHAPE_ARROW: 'arrow',
	
	/**
	 * Variable: SHAPE_ARROW_CONNECTOR
	 * 
	 * Name under which  is registered in .
	 * Default is arrowConnector.
	 */
	SHAPE_ARROW_CONNECTOR: 'arrowConnector',
	
	/**
	 * Variable: SHAPE_LABEL
	 * 
	 * Name under which  is registered in .
	 * Default is label.
	 */
	SHAPE_LABEL: 'label',
	
	/**
	 * Variable: SHAPE_CYLINDER
	 * 
	 * Name under which  is registered in .
	 * Default is cylinder.
	 */
	SHAPE_CYLINDER: 'cylinder',
	
	/**
	 * Variable: SHAPE_SWIMLANE
	 * 
	 * Name under which  is registered in .
	 * Default is swimlane.
	 */
	SHAPE_SWIMLANE: 'swimlane',
		
	/**
	 * Variable: SHAPE_CONNECTOR
	 * 
	 * Name under which  is registered in .
	 * Default is connector.
	 */
	SHAPE_CONNECTOR: 'connector',

	/**
	 * Variable: SHAPE_ACTOR
	 * 
	 * Name under which  is registered in .
	 * Default is actor.
	 */
	SHAPE_ACTOR: 'actor',
		
	/**
	 * Variable: SHAPE_CLOUD
	 * 
	 * Name under which  is registered in .
	 * Default is cloud.
	 */
	SHAPE_CLOUD: 'cloud',
		
	/**
	 * Variable: SHAPE_TRIANGLE
	 * 
	 * Name under which  is registered in .
	 * Default is triangle.
	 */
	SHAPE_TRIANGLE: 'triangle',
		
	/**
	 * Variable: SHAPE_HEXAGON
	 * 
	 * Name under which  is registered in .
	 * Default is hexagon.
	 */
	SHAPE_HEXAGON: 'hexagon',

	/**
	 * Variable: ARROW_CLASSIC
	 * 
	 * Constant for classic arrow markers.
	 */
	ARROW_CLASSIC: 'classic',

	/**
	 * Variable: ARROW_CLASSIC_THIN
	 * 
	 * Constant for thin classic arrow markers.
	 */
	ARROW_CLASSIC_THIN: 'classicThin',

	/**
	 * Variable: ARROW_BLOCK
	 * 
	 * Constant for block arrow markers.
	 */
	ARROW_BLOCK: 'block',

	/**
	 * Variable: ARROW_BLOCK_THIN
	 * 
	 * Constant for thin block arrow markers.
	 */
	ARROW_BLOCK_THIN: 'blockThin',

	/**
	 * Variable: ARROW_OPEN
	 * 
	 * Constant for open arrow markers.
	 */
	ARROW_OPEN: 'open',

	/**
	 * Variable: ARROW_OPEN_THIN
	 * 
	 * Constant for thin open arrow markers.
	 */
	ARROW_OPEN_THIN: 'openThin',

	/**
	 * Variable: ARROW_OVAL
	 * 
	 * Constant for oval arrow markers.
	 */
	ARROW_OVAL: 'oval',

	/**
	 * Variable: ARROW_DIAMOND
	 * 
	 * Constant for diamond arrow markers.
	 */
	ARROW_DIAMOND: 'diamond',

	/**
	 * Variable: ARROW_DIAMOND_THIN
	 * 
	 * Constant for thin diamond arrow markers.
	 */
	ARROW_DIAMOND_THIN: 'diamondThin',

	/**
	 * Variable: ALIGN_LEFT
	 * 
	 * Constant for left horizontal alignment. Default is left.
	 */
	ALIGN_LEFT: 'left',

	/**
	 * Variable: ALIGN_CENTER
	 * 
	 * Constant for center horizontal alignment. Default is center.
	 */
	ALIGN_CENTER: 'center',

	/**
	 * Variable: ALIGN_RIGHT
	 * 
	 * Constant for right horizontal alignment. Default is right.
	 */
	ALIGN_RIGHT: 'right',

	/**
	 * Variable: ALIGN_TOP
	 * 
	 * Constant for top vertical alignment. Default is top.
	 */
	ALIGN_TOP: 'top',

	/**
	 * Variable: ALIGN_MIDDLE
	 * 
	 * Constant for middle vertical alignment. Default is middle.
	 */
	ALIGN_MIDDLE: 'middle',

	/**
	 * Variable: ALIGN_BOTTOM
	 * 
	 * Constant for bottom vertical alignment. Default is bottom.
	 */
	ALIGN_BOTTOM: 'bottom',

	/**
	 * Variable: DIRECTION_NORTH
	 * 
	 * Constant for direction north. Default is north.
	 */
	DIRECTION_NORTH: 'north',

	/**
	 * Variable: DIRECTION_SOUTH
	 * 
	 * Constant for direction south. Default is south.
	 */
	DIRECTION_SOUTH: 'south',

	/**
	 * Variable: DIRECTION_EAST
	 * 
	 * Constant for direction east. Default is east.
	 */
	DIRECTION_EAST: 'east',

	/**
	 * Variable: DIRECTION_WEST
	 * 
	 * Constant for direction west. Default is west.
	 */
	DIRECTION_WEST: 'west',

	/**
	 * Variable: TEXT_DIRECTION_DEFAULT
	 * 
	 * Constant for text direction default. Default is an empty string. Use
	 * this value to use the default text direction of the operating system. 
	 */
	TEXT_DIRECTION_DEFAULT: '',

	/**
	 * Variable: TEXT_DIRECTION_AUTO
	 * 
	 * Constant for text direction automatic. Default is auto. Use this value
	 * to find the direction for a given text with . 
	 */
	TEXT_DIRECTION_AUTO: 'auto',

	/**
	 * Variable: TEXT_DIRECTION_LTR
	 * 
	 * Constant for text direction left to right. Default is ltr. Use this
	 * value for left to right text direction.
	 */
	TEXT_DIRECTION_LTR: 'ltr',

	/**
	 * Variable: TEXT_DIRECTION_RTL
	 * 
	 * Constant for text direction right to left. Default is rtl. Use this
	 * value for right to left text direction.
	 */
	TEXT_DIRECTION_RTL: 'rtl',

	/**
	 * Variable: DIRECTION_MASK_NONE
	 * 
	 * Constant for no direction.
	 */
	DIRECTION_MASK_NONE: 0,

	/**
	 * Variable: DIRECTION_MASK_WEST
	 * 
	 * Bitwise mask for west direction.
	 */
	DIRECTION_MASK_WEST: 1,
	
	/**
	 * Variable: DIRECTION_MASK_NORTH
	 * 
	 * Bitwise mask for north direction.
	 */
	DIRECTION_MASK_NORTH: 2,

	/**
	 * Variable: DIRECTION_MASK_SOUTH
	 * 
	 * Bitwise mask for south direction.
	 */
	DIRECTION_MASK_SOUTH: 4,

	/**
	 * Variable: DIRECTION_MASK_EAST
	 * 
	 * Bitwise mask for east direction.
	 */
	DIRECTION_MASK_EAST: 8,
	
	/**
	 * Variable: DIRECTION_MASK_ALL
	 * 
	 * Bitwise mask for all directions.
	 */
	DIRECTION_MASK_ALL: 15,

	/**
	 * Variable: ELBOW_VERTICAL
	 * 
	 * Constant for elbow vertical. Default is horizontal.
	 */
	ELBOW_VERTICAL: 'vertical',

	/**
	 * Variable: ELBOW_HORIZONTAL
	 * 
	 * Constant for elbow horizontal. Default is horizontal.
	 */
	ELBOW_HORIZONTAL: 'horizontal',

	/**
	 * Variable: EDGESTYLE_ELBOW
	 * 
	 * Name of the elbow edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_ELBOW: 'elbowEdgeStyle',

	/**
	 * Variable: EDGESTYLE_ENTITY_RELATION
	 * 
	 * Name of the entity relation edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_ENTITY_RELATION: 'entityRelationEdgeStyle',

	/**
	 * Variable: EDGESTYLE_LOOP
	 * 
	 * Name of the loop edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_LOOP: 'loopEdgeStyle',

	/**
	 * Variable: EDGESTYLE_SIDETOSIDE
	 * 
	 * Name of the side to side edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_SIDETOSIDE: 'sideToSideEdgeStyle',

	/**
	 * Variable: EDGESTYLE_TOPTOBOTTOM
	 * 
	 * Name of the top to bottom edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_TOPTOBOTTOM: 'topToBottomEdgeStyle',

	/**
	 * Variable: EDGESTYLE_ORTHOGONAL
	 * 
	 * Name of the generic orthogonal edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_ORTHOGONAL: 'orthogonalEdgeStyle',

	/**
	 * Variable: EDGESTYLE_SEGMENT
	 * 
	 * Name of the generic segment edge style. Can be used as a string value
	 * for the STYLE_EDGE style.
	 */
	EDGESTYLE_SEGMENT: 'segmentEdgeStyle',
 
	/**
	 * Variable: PERIMETER_ELLIPSE
	 * 
	 * Name of the ellipse perimeter. Can be used as a string value
	 * for the STYLE_PERIMETER style.
	 */
	PERIMETER_ELLIPSE: 'ellipsePerimeter',

	/**
	 * Variable: PERIMETER_RECTANGLE
	 *
	 * Name of the rectangle perimeter. Can be used as a string value
	 * for the STYLE_PERIMETER style.
	 */
	PERIMETER_RECTANGLE: 'rectanglePerimeter',

	/**
	 * Variable: PERIMETER_RHOMBUS
	 * 
	 * Name of the rhombus perimeter. Can be used as a string value
	 * for the STYLE_PERIMETER style.
	 */
	PERIMETER_RHOMBUS: 'rhombusPerimeter',

	/**
	 * Variable: PERIMETER_HEXAGON
	 * 
	 * Name of the hexagon perimeter. Can be used as a string value 
	 * for the STYLE_PERIMETER style.
	 */
	PERIMETER_HEXAGON: 'hexagonPerimeter',

	/**
	 * Variable: PERIMETER_TRIANGLE
	 * 
	 * Name of the triangle perimeter. Can be used as a string value
	 * for the STYLE_PERIMETER style.
	 */
	PERIMETER_TRIANGLE: 'trianglePerimeter'
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxEventObject
 * 
 * The mxEventObject is a wrapper for all properties of a single event.
 * Additionally, it also offers functions to consume the event and check if it
 * was consumed as follows:
 * 
 * (code)
 * evt.consume();
 * INV: evt.isConsumed() == true
 * (end)
 * 
 * Constructor: mxEventObject
 *
 * Constructs a new event object with the specified name. An optional
 * sequence of key, value pairs can be appended to define properties.
 * 
 * Example:
 *
 * (code)
 * new mxEventObject("eventName", key1, val1, .., keyN, valN)
 * (end)
 */
function mxEventObject(name)
{
	this.name = name;
	this.properties = [];
	
	for (var i = 1; i < arguments.length; i += 2)
	{
		if (arguments[i + 1] != null)
		{
			this.properties[arguments[i]] = arguments[i + 1];
		}
	}
};

/**
 * Variable: name
 *
 * Holds the name.
 */
mxEventObject.prototype.name = null;

/**
 * Variable: properties
 *
 * Holds the properties as an associative array.
 */
mxEventObject.prototype.properties = null;

/**
 * Variable: consumed
 *
 * Holds the consumed state. Default is false.
 */
mxEventObject.prototype.consumed = false;

/**
 * Function: getName
 * 
 * Returns .
 */
mxEventObject.prototype.getName = function()
{
	return this.name;
};

/**
 * Function: getProperties
 * 
 * Returns .
 */
mxEventObject.prototype.getProperties = function()
{
	return this.properties;
};

/**
 * Function: getProperty
 * 
 * Returns the property for the given key.
 */
mxEventObject.prototype.getProperty = function(key)
{
	return this.properties[key];
};

/**
 * Function: isConsumed
 *
 * Returns true if the event has been consumed.
 */
mxEventObject.prototype.isConsumed = function()
{
	return this.consumed;
};

/**
 * Function: consume
 *
 * Consumes the event.
 */
mxEventObject.prototype.consume = function()
{
	this.consumed = true;
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxMouseEvent
 * 
 * Base class for all mouse events in mxGraph. A listener for this event should
 * implement the following methods:
 * 
 * (code)
 * graph.addMouseListener(
 * {
 *   mouseDown: function(sender, evt)
 *   {
 *     mxLog.debug('mouseDown');
 *   },
 *   mouseMove: function(sender, evt)
 *   {
 *     mxLog.debug('mouseMove');
 *   },
 *   mouseUp: function(sender, evt)
 *   {
 *     mxLog.debug('mouseUp');
 *   }
 * });
 * (end)
 * 
 * Constructor: mxMouseEvent
 *
 * Constructs a new event object for the given arguments.
 * 
 * Parameters:
 * 
 * evt - Native mouse event.
 * state - Optional  under the mouse.
 * 
 */
function mxMouseEvent(evt, state)
{
	this.evt = evt;
	this.state = state;
	this.sourceState = state;
};

/**
 * Variable: consumed
 *
 * Holds the consumed state of this event.
 */
mxMouseEvent.prototype.consumed = false;

/**
 * Variable: evt
 *
 * Holds the inner event object.
 */
mxMouseEvent.prototype.evt = null;

/**
 * Variable: graphX
 *
 * Holds the x-coordinate of the event in the graph. This value is set in
 * .
 */
mxMouseEvent.prototype.graphX = null;

/**
 * Variable: graphY
 *
 * Holds the y-coordinate of the event in the graph. This value is set in
 * .
 */
mxMouseEvent.prototype.graphY = null;

/**
 * Variable: state
 *
 * Holds the optional  associated with this event.
 */
mxMouseEvent.prototype.state = null;

/**
 * Variable: sourceState
 * 
 * Holds the  that was passed to the constructor. This can be
 * different from  depending on the result of .
 */
mxMouseEvent.prototype.sourceState = null;

/**
 * Function: getEvent
 * 
 * Returns .
 */
mxMouseEvent.prototype.getEvent = function()
{
	return this.evt;
};

/**
 * Function: getSource
 * 
 * Returns the target DOM element using  for .
 */
mxMouseEvent.prototype.getSource = function()
{
	return mxEvent.getSource(this.evt);
};

/**
 * Function: isSource
 * 
 * Returns true if the given  is the source of .
 */
mxMouseEvent.prototype.isSource = function(shape)
{
	if (shape != null)
	{
		return mxUtils.isAncestorNode(shape.node, this.getSource());
	}
	
	return false;
};

/**
 * Function: getX
 * 
 * Returns .
 */
mxMouseEvent.prototype.getX = function()
{
	return mxEvent.getClientX(this.getEvent());
};

/**
 * Function: getY
 * 
 * Returns .
 */
mxMouseEvent.prototype.getY = function()
{
	return mxEvent.getClientY(this.getEvent());
};

/**
 * Function: getGraphX
 * 
 * Returns .
 */
mxMouseEvent.prototype.getGraphX = function()
{
	return this.graphX;
};

/**
 * Function: getGraphY
 * 
 * Returns .
 */
mxMouseEvent.prototype.getGraphY = function()
{
	return this.graphY;
};

/**
 * Function: getState
 * 
 * Returns .
 */
mxMouseEvent.prototype.getState = function()
{
	return this.state;
};

/**
 * Function: getCell
 * 
 * Returns the  in  is not null.
 */
mxMouseEvent.prototype.getCell = function()
{
	var state = this.getState();
	
	if (state != null)
	{
		return state.cell;
	}
	
	return null;
};

/**
 * Function: isPopupTrigger
 *
 * Returns true if the event is a popup trigger.
 */
mxMouseEvent.prototype.isPopupTrigger = function()
{
	return mxEvent.isPopupTrigger(this.getEvent());
};

/**
 * Function: isConsumed
 *
 * Returns .
 */
mxMouseEvent.prototype.isConsumed = function()
{
	return this.consumed;
};

/**
 * Function: consume
 *
 * Sets  to true and invokes preventDefault on the native event
 * if such a method is defined. This is used mainly to avoid the cursor from
 * being changed to a text cursor in Webkit. You can use the preventDefault
 * flag to disable this functionality.
 * 
 * Parameters:
 * 
 * preventDefault - Specifies if the native event should be canceled. Default
 * is true.
 */
mxMouseEvent.prototype.consume = function(preventDefault)
{
	preventDefault = (preventDefault != null) ? preventDefault :
		(this.evt.touches != null || mxEvent.isMouseEvent(this.evt));
	
	if (preventDefault && this.evt.preventDefault)
	{
		this.evt.preventDefault();
	}

	// Workaround for images being dragged in IE
	// Does not change returnValue in Opera
	if (mxClient.IS_IE)
	{
		this.evt.returnValue = true;
	}

	// Sets local consumed state
	this.consumed = true;
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxEventSource
 *
 * Base class for objects that dispatch named events. To create a subclass that
 * inherits from mxEventSource, the following code is used.
 *
 * (code)
 * function MyClass() { };
 *
 * MyClass.prototype = new mxEventSource();
 * MyClass.prototype.constructor = MyClass;
 * (end)
 *
 * Known Subclasses:
 *
 * , , , , ,
 * , 
 * 
 * Constructor: mxEventSource
 *
 * Constructs a new event source.
 */
function mxEventSource(eventSource)
{
	this.setEventSource(eventSource);
};

/**
 * Variable: eventListeners
 *
 * Holds the event names and associated listeners in an array. The array
 * contains the event name followed by the respective listener for each
 * registered listener.
 */
mxEventSource.prototype.eventListeners = null;

/**
 * Variable: eventsEnabled
 *
 * Specifies if events can be fired. Default is true.
 */
mxEventSource.prototype.eventsEnabled = true;

/**
 * Variable: eventSource
 *
 * Optional source for events. Default is null.
 */
mxEventSource.prototype.eventSource = null;

/**
 * Function: isEventsEnabled
 * 
 * Returns .
 */
mxEventSource.prototype.isEventsEnabled = function()
{
	return this.eventsEnabled;
};

/**
 * Function: setEventsEnabled
 * 
 * Sets .
 */
mxEventSource.prototype.setEventsEnabled = function(value)
{
	this.eventsEnabled = value;
};

/**
 * Function: getEventSource
 * 
 * Returns .
 */
mxEventSource.prototype.getEventSource = function()
{
	return this.eventSource;
};

/**
 * Function: setEventSource
 * 
 * Sets .
 */
mxEventSource.prototype.setEventSource = function(value)
{
	this.eventSource = value;
};

/**
 * Function: addListener
 *
 * Binds the specified function to the given event name. If no event name
 * is given, then the listener is registered for all events.
 * 
 * The parameters of the listener are the sender and an .
 */
mxEventSource.prototype.addListener = function(name, funct)
{
	if (this.eventListeners == null)
	{
		this.eventListeners = [];
	}
	
	this.eventListeners.push(name);
	this.eventListeners.push(funct);
};

/**
 * Function: removeListener
 *
 * Removes all occurrences of the given listener from .
 */
mxEventSource.prototype.removeListener = function(funct)
{
	if (this.eventListeners != null)
	{
		var i = 0;
		
		while (i < this.eventListeners.length)
		{
			if (this.eventListeners[i+1] == funct)
			{
				this.eventListeners.splice(i, 2);
			}
			else
			{
				i += 2;
			}
		}
	}
};

/**
 * Function: fireEvent
 *
 * Dispatches the given event to the listeners which are registered for
 * the event. The sender argument is optional. The current execution scope
 * ("this") is used for the listener invocation (see ).
 *
 * Example:
 *
 * (code)
 * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN))
 * (end)
 * 
 * Parameters:
 *
 * evt -  that represents the event.
 * sender - Optional sender to be passed to the listener. Default value is
 * the return value of .
 */
mxEventSource.prototype.fireEvent = function(evt, sender)
{
	if (this.eventListeners != null && this.isEventsEnabled())
	{
		if (evt == null)
		{
			evt = new mxEventObject();
		}
		
		if (sender == null)
		{
			sender = this.getEventSource();
		}

		if (sender == null)
		{
			sender = this;
		}

		var args = [sender, evt];
		
		for (var i = 0; i < this.eventListeners.length; i += 2)
		{
			var listen = this.eventListeners[i];
			
			if (listen == null || listen == evt.getName())
			{
				this.eventListeners[i+1].apply(this, args);
			}
		}
	}
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxEvent =
{

	/**
	 * Class: mxEvent
	 * 
	 * Cross-browser DOM event support. For internal event handling,
	 *  and the graph event dispatch loop in  are used.
	 * 
	 * Memory Leaks:
	 * 
	 * Use this class for adding and removing listeners to/from DOM nodes. The
	 *  function is provided to remove all listeners that
	 * have been added using . The function should be invoked when
	 * the last reference is removed in the JavaScript code, typically when the
	 * referenced DOM node is removed from the DOM.
	 *
	 * Function: addListener
	 * 
	 * Binds the function to the specified event on the given element. Use
	 *  in order to bind the "this" keyword inside the function
	 * to a given execution scope.
	 */
	addListener: function()
	{
		var updateListenerList = function(element, eventName, funct)
		{
			if (element.mxListenerList == null)
			{
				element.mxListenerList = [];
			}
			
			var entry = {name: eventName, f: funct};
			element.mxListenerList.push(entry);
		};
		
		if (window.addEventListener)
		{
			return function(element, eventName, funct)
			{
				element.addEventListener(eventName, funct, false);
				updateListenerList(element, eventName, funct);
			};
		}
		else
		{
			return function(element, eventName, funct)
			{
				element.attachEvent('on' + eventName, funct);
				updateListenerList(element, eventName, funct);				
			};
		}
	}(),

	/**
	 * Function: removeListener
	 *
	 * Removes the specified listener from the given element.
	 */
	removeListener: function()
	{
		var updateListener = function(element, eventName, funct)
		{
			if (element.mxListenerList != null)
			{
				var listenerCount = element.mxListenerList.length;
				
				for (var i = 0; i < listenerCount; i++)
				{
					var entry = element.mxListenerList[i];
					
					if (entry.f == funct)
					{
						element.mxListenerList.splice(i, 1);
						break;
					}
				}
				
				if (element.mxListenerList.length == 0)
				{
					element.mxListenerList = null;
				}
			}
		};
		
		if (window.removeEventListener)
		{
			return function(element, eventName, funct)
			{
				element.removeEventListener(eventName, funct, false);
				updateListener(element, eventName, funct);
			};
		}
		else
		{
			return function(element, eventName, funct)
			{
				element.detachEvent('on' + eventName, funct);
				updateListener(element, eventName, funct);
			};
		}
	}(),

	/**
	 * Function: removeAllListeners
	 * 
	 * Removes all listeners from the given element.
	 */
	removeAllListeners: function(element)
	{
		var list = element.mxListenerList;

		if (list != null)
		{
			while (list.length > 0)
			{
				var entry = list[0];
				mxEvent.removeListener(element, entry.name, entry.f);
			}
		}
	},
	
	/**
	 * Function: addGestureListeners
	 * 
	 * Adds the given listeners for touch, mouse and/or pointer events. If
	 *  is true then pointer events will be registered,
	 * else the respective mouse events will be registered. If 
	 * is false and  is true then the respective touch events
	 * will be registered as well as the mouse events.
	 */
	addGestureListeners: function(node, startListener, moveListener, endListener)
	{
		if (startListener != null)
		{
			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', startListener);
		}
		
		if (moveListener != null)
		{
			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', moveListener);
		}
		
		if (endListener != null)
		{
			mxEvent.addListener(node, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', endListener);
		}
		
		if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
		{
			if (startListener != null)
			{
				mxEvent.addListener(node, 'touchstart', startListener);
			}
			
			if (moveListener != null)
			{
				mxEvent.addListener(node, 'touchmove', moveListener);
			}
			
			if (endListener != null)
			{
				mxEvent.addListener(node, 'touchend', endListener);
			}
		}
	},
	
	/**
	 * Function: removeGestureListeners
	 * 
	 * Removes the given listeners from mousedown, mousemove, mouseup and the
	 * respective touch events if  is true.
	 */
	removeGestureListeners: function(node, startListener, moveListener, endListener)
	{
		if (startListener != null)
		{
			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', startListener);
		}
		
		if (moveListener != null)
		{
			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', moveListener);
		}
		
		if (endListener != null)
		{
			mxEvent.removeListener(node, (mxClient.IS_POINTER) ? 'pointerup' : 'mouseup', endListener);
		}
		
		if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
		{
			if (startListener != null)
			{
				mxEvent.removeListener(node, 'touchstart', startListener);
			}
			
			if (moveListener != null)
			{
				mxEvent.removeListener(node, 'touchmove', moveListener);
			}
			
			if (endListener != null)
			{
				mxEvent.removeListener(node, 'touchend', endListener);
			}
		}
	},
	
	/**
	 * Function: redirectMouseEvents
	 *
	 * Redirects the mouse events from the given DOM node to the graph dispatch
	 * loop using the event and given state as event arguments. State can
	 * either be an instance of  or a function that returns an
	 * . The down, move, up and dblClick arguments are optional
	 * functions that take the trigger event as arguments and replace the
	 * default behaviour.
	 */
	redirectMouseEvents: function(node, graph, state, down, move, up, dblClick)
	{
		var getState = function(evt)
		{
			return (typeof(state) == 'function') ? state(evt) : state;
		};
		
		mxEvent.addGestureListeners(node, function (evt)
		{
			if (down != null)
			{
				down(evt);
			}
			else if (!mxEvent.isConsumed(evt))
			{
				graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, getState(evt)));
			}
		},
		function (evt)
		{
			if (move != null)
			{
				move(evt);
			}
			else if (!mxEvent.isConsumed(evt))
			{
				graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
			}
		},
		function (evt)
		{
			if (up != null)
			{
				up(evt);
			}
			else if (!mxEvent.isConsumed(evt))
			{
				graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt, getState(evt)));
			}
		});

		mxEvent.addListener(node, 'dblclick', function (evt)
		{
			if (dblClick != null)
			{
				dblClick(evt);
			}
			else if (!mxEvent.isConsumed(evt))
			{
				var tmp = getState(evt);
				graph.dblClick(evt, (tmp != null) ? tmp.cell : null);
			}
		});
	},

	/**
	 * Function: release
	 * 
	 * Removes the known listeners from the given DOM node and its descendants.
	 * 
	 * Parameters:
	 * 
	 * element - DOM node to remove the listeners from.
	 */
	release: function(element)
	{
		try
		{
			if (element != null)
			{
				mxEvent.removeAllListeners(element);
				
				var children = element.childNodes;
				
				if (children != null)
				{
			        var childCount = children.length;
			        
			        for (var i = 0; i < childCount; i += 1)
			        {
			        	mxEvent.release(children[i]);
			        }
			    }
			}
		}
		catch (e)
		{
			// ignores errors as this is typically called in cleanup code
		}
	},

	/**
	 * Function: addMouseWheelListener
	 * 
	 * Installs the given function as a handler for mouse wheel events. The
	 * function has two arguments: the mouse event and a boolean that specifies
	 * if the wheel was moved up or down.
	 * 
	 * This has been tested with IE 6 and 7, Firefox (all versions), Opera and
	 * Safari. It does currently not work on Safari for Mac.
	 * 
	 * Example:
	 * 
	 * (code)
	 * mxEvent.addMouseWheelListener(function (evt, up)
	 * {
	 *   mxLog.show();
	 *   mxLog.debug('mouseWheel: up='+up);
	 * });
	 *(end)
	 * 
	 * Parameters:
	 * 
	 * funct - Handler function that takes the event argument and a boolean up
	 * argument for the mousewheel direction.
	 * target - Target for installing the listener in Google Chrome. See 
	 * https://www.chromestatus.com/features/6662647093133312.
	 */
	addMouseWheelListener: function(funct, target)
	{
		if (funct != null)
		{
			var wheelHandler = function(evt)
			{
				// IE does not give an event object but the
				// global event object is the mousewheel event
				// at this point in time.
				if (evt == null)
				{
					evt = window.event;
				}
			
				//To prevent window zoom on trackpad pinch
				if (evt.ctrlKey) 
				{
					evt.preventDefault();
				}

				var delta = -evt.deltaY;
				
				// Handles the event using the given function
				if (Math.abs(evt.deltaX) > 0.5 || Math.abs(evt.deltaY) > 0.5)
				{
					funct(evt, (evt.deltaY == 0) ?  -evt.deltaX > 0 : -evt.deltaY > 0);
				}
			};
	
			target = target != null ? target : window;
					
			if (mxClient.IS_SF && !mxClient.IS_TOUCH)
			{
				var scale = 1;
				
				mxEvent.addListener(target, 'gesturestart', function(evt)
				{
					mxEvent.consume(evt);
					scale = 1;
				});
				
				mxEvent.addListener(target, 'gesturechange', function(evt)
				{
					mxEvent.consume(evt);
					var diff = scale - evt.scale;
					
					if (Math.abs(diff) > 0.2)
					{
						funct(evt, diff < 0, true);
						scale = evt.scale;
					}
				});

				mxEvent.addListener(target, 'gestureend', function(evt)
				{
					mxEvent.consume(evt);
				});
			}
			
			mxEvent.addListener(target, 'wheel', wheelHandler);
		}
	},
	
	/**
	 * Function: disableContextMenu
	 *
	 * Disables the context menu for the given element.
	 */
	disableContextMenu: function(element)
	{
		mxEvent.addListener(element, 'contextmenu', function(evt)
		{
			if (evt.preventDefault)
			{
				evt.preventDefault();
			}
			
			return false;
		});
	},
	
	/**
	 * Function: getSource
	 * 
	 * Returns the event's target or srcElement depending on the browser.
	 */
	getSource: function(evt)
	{
		return (evt.srcElement != null) ? evt.srcElement : evt.target;
	},

	/**
	 * Function: isConsumed
	 * 
	 * Returns true if the event has been consumed using .
	 */
	isConsumed: function(evt)
	{
		return evt.isConsumed != null && evt.isConsumed;
	},

	/**
	 * Function: isTouchEvent
	 * 
	 * Returns true if the event was generated using a touch device (not a pen or mouse).
	 */
	isTouchEvent: function(evt)
	{
		return (evt.pointerType != null) ? (evt.pointerType == 'touch' || evt.pointerType ===
			evt.MSPOINTER_TYPE_TOUCH) : ((evt.mozInputSource != null) ?
					evt.mozInputSource == 5 : evt.type.indexOf('touch') == 0);
	},

	/**
	 * Function: isPenEvent
	 * 
	 * Returns true if the event was generated using a pen (not a touch device or mouse).
	 */
	isPenEvent: function(evt)
	{
		return (evt.pointerType != null) ? (evt.pointerType == 'pen' || evt.pointerType ===
			evt.MSPOINTER_TYPE_PEN) : ((evt.mozInputSource != null) ?
					evt.mozInputSource == 2 : evt.type.indexOf('pen') == 0);
	},

	/**
	 * Function: isMultiTouchEvent
	 * 
	 * Returns true if the event was generated using a touch device (not a pen or mouse).
	 */
	isMultiTouchEvent: function(evt)
	{
		return (evt.type != null && evt.type.indexOf('touch') == 0 && evt.touches != null && evt.touches.length > 1);
	},

	/**
	 * Function: isMouseEvent
	 * 
	 * Returns true if the event was generated using a mouse (not a pen or touch device).
	 */
	isMouseEvent: function(evt)
	{
		return (evt.pointerType != null) ? (evt.pointerType == 'mouse' || evt.pointerType ===
			evt.MSPOINTER_TYPE_MOUSE) : ((evt.mozInputSource != null) ?
				evt.mozInputSource == 1 : evt.type.indexOf('mouse') == 0);
	},
	
	/**
	 * Function: isLeftMouseButton
	 * 
	 * Returns true if the left mouse button is pressed for the given event.
	 * To check if a button is pressed during a mouseMove you should use the
	 *  property. Note that this returns true in Firefox
	 * for control+left-click on the Mac.
	 */
	isLeftMouseButton: function(evt)
	{
		// Special case for mousemove and mousedown we check the buttons
		// if it exists because which is 0 even if no button is pressed
		if ('buttons' in evt && (evt.type == 'mousedown' || evt.type == 'mousemove'))
		{
			return evt.buttons == 1;
		}
		else if ('which' in evt)
		{
	        return evt.which === 1;
	    }
		else
		{
	        return evt.button === 1;
	    }
	},
	
	/**
	 * Function: isMiddleMouseButton
	 * 
	 * Returns true if the middle mouse button is pressed for the given event.
	 * To check if a button is pressed during a mouseMove you should use the
	 *  property.
	 */
	isMiddleMouseButton: function(evt)
	{
		if ('which' in evt)
		{
	        return evt.which === 2;
	    }
		else
		{
	        return evt.button === 4;
	    }
	},
	
	/**
	 * Function: isRightMouseButton
	 * 
	 * Returns true if the right mouse button was pressed. Note that this
	 * button might not be available on some systems. For handling a popup
	 * trigger  should be used.
	 */
	isRightMouseButton: function(evt)
	{
		if ('which' in evt)
		{
	        return evt.which === 3;
	    }
		else
		{
	        return evt.button === 2;
	    }
	},

	/**
	 * Function: isPopupTrigger
	 * 
	 * Returns true if the event is a popup trigger. This implementation
	 * returns true if the right button or the left button and control was
	 * pressed on a Mac.
	 */
	isPopupTrigger: function(evt)
	{
		return mxEvent.isRightMouseButton(evt) || (mxClient.IS_MAC && mxEvent.isControlDown(evt) &&
			!mxEvent.isShiftDown(evt) && !mxEvent.isMetaDown(evt) && !mxEvent.isAltDown(evt));
	},

	/**
	 * Function: isShiftDown
	 * 
	 * Returns true if the shift key is pressed for the given event.
	 */
	isShiftDown: function(evt)
	{
		return (evt != null) ? evt.shiftKey : false;
	},

	/**
	 * Function: isAltDown
	 * 
	 * Returns true if the alt key is pressed for the given event.
	 */
	isAltDown: function(evt)
	{
		return (evt != null) ? evt.altKey : false;
	},

	/**
	 * Function: isControlDown
	 * 
	 * Returns true if the control key is pressed for the given event.
	 */
	isControlDown: function(evt)
	{
		return (evt != null) ? evt.ctrlKey : false;
	},

	/**
	 * Function: isMetaDown
	 * 
	 * Returns true if the meta key is pressed for the given event.
	 */
	isMetaDown: function(evt)
	{
		return (evt != null) ? evt.metaKey : false;
	},

	/**
	 * Function: getMainEvent
	 * 
	 * Returns the touch or mouse event that contains the mouse coordinates.
	 */
	getMainEvent: function(e)
	{
		if ((e.type == 'touchstart' || e.type == 'touchmove') && e.touches != null && e.touches[0] != null)
		{
			e = e.touches[0];
		}
		else if (e.type == 'touchend' && e.changedTouches != null && e.changedTouches[0] != null)
		{
			e = e.changedTouches[0];
		}
		
		return e;
	},
	
	/**
	 * Function: getClientX
	 * 
	 * Returns true if the meta key is pressed for the given event.
	 */
	getClientX: function(e)
	{
		return mxEvent.getMainEvent(e).clientX;
	},

	/**
	 * Function: getClientY
	 * 
	 * Returns true if the meta key is pressed for the given event.
	 */
	getClientY: function(e)
	{
		return mxEvent.getMainEvent(e).clientY;
	},

	/**
	 * Function: consume
	 * 
	 * Consumes the given event.
	 * 
	 * Parameters:
	 * 
	 * evt - Native event to be consumed.
	 * preventDefault - Optional boolean to prevent the default for the event.
	 * Default is true.
	 * stopPropagation - Option boolean to stop event propagation. Default is
	 * true.
	 */
	consume: function(evt, preventDefault, stopPropagation)
	{
		preventDefault = (preventDefault != null) ? preventDefault : true;
		stopPropagation = (stopPropagation != null) ? stopPropagation : true;
		
		if (preventDefault)
		{
			if (evt.preventDefault)
			{
				if (stopPropagation)
				{
					evt.stopPropagation();
				}
				
				evt.preventDefault();
			}
			else if (stopPropagation)
			{
				evt.cancelBubble = true;
			}
		}

		// Opera
		evt.isConsumed = true;

		// Other browsers
		if (!evt.preventDefault)
		{
			evt.returnValue = false;
		}
	},
	
	//
	// Special handles in mouse events
	//
	
	/**
	 * Variable: LABEL_HANDLE
	 * 
	 * Index for the label handle in an mxMouseEvent. This should be a negative
	 * value that does not interfere with any possible handle indices. Default
	 * is -1.
	 */
	LABEL_HANDLE: -1,
	
	/**
	 * Variable: ROTATION_HANDLE
	 * 
	 * Index for the rotation handle in an mxMouseEvent. This should be a
	 * negative value that does not interfere with any possible handle indices.
	 * Default is -2.
	 */
	ROTATION_HANDLE: -2,
	
	/**
	 * Variable: CUSTOM_HANDLE
	 * 
	 * Start index for the custom handles in an mxMouseEvent. This should be a
	 * negative value and is the start index which is decremented for each
	 * custom handle. Default is -100.
	 */
	CUSTOM_HANDLE: -100,
	
	/**
	 * Variable: VIRTUAL_HANDLE
	 * 
	 * Start index for the virtual handles in an mxMouseEvent. This should be a
	 * negative value and is the start index which is decremented for each
	 * virtual handle. Default is -100000. This assumes that there are no more
	 * than VIRTUAL_HANDLE - CUSTOM_HANDLE custom handles.
	 * 
	 */
	VIRTUAL_HANDLE: -100000,
	
	//
	// Event names
	//
	
	/**
	 * Variable: MOUSE_DOWN
	 *
	 * Specifies the event name for mouseDown.
	 */
	MOUSE_DOWN: 'mouseDown',
	
	/**
	 * Variable: MOUSE_MOVE
	 *
	 * Specifies the event name for mouseMove. 
	 */
	MOUSE_MOVE: 'mouseMove',
	
	/**
	 * Variable: MOUSE_UP
	 *
	 * Specifies the event name for mouseUp. 
	 */
	MOUSE_UP: 'mouseUp',

	/**
	 * Variable: ACTIVATE
	 *
	 * Specifies the event name for activate.
	 */
	ACTIVATE: 'activate',

	/**
	 * Variable: RESIZE_START
	 *
	 * Specifies the event name for resizeStart.
	 */
	RESIZE_START: 'resizeStart',

	/**
	 * Variable: RESIZE
	 *
	 * Specifies the event name for resize.
	 */
	RESIZE: 'resize',

	/**
	 * Variable: RESIZE_END
	 *
	 * Specifies the event name for resizeEnd.
	 */
	RESIZE_END: 'resizeEnd',

	/**
	 * Variable: MOVE_START
	 *
	 * Specifies the event name for moveStart.
	 */
	MOVE_START: 'moveStart',

	/**
	 * Variable: MOVE
	 *
	 * Specifies the event name for move.
	 */
	MOVE: 'move',

	/**
	 * Variable: MOVE_END
	 *
	 * Specifies the event name for moveEnd.
	 */
	MOVE_END: 'moveEnd',

	/**
	 * Variable: PAN_START
	 *
	 * Specifies the event name for panStart.
	 */
	PAN_START: 'panStart',

	/**
	 * Variable: PAN
	 *
	 * Specifies the event name for pan.
	 */
	PAN: 'pan',

	/**
	 * Variable: PAN_END
	 *
	 * Specifies the event name for panEnd.
	 */
	PAN_END: 'panEnd',

	/**
	 * Variable: MINIMIZE
	 *
	 * Specifies the event name for minimize.
	 */
	MINIMIZE: 'minimize',

	/**
	 * Variable: NORMALIZE
	 *
	 * Specifies the event name for normalize.
	 */
	NORMALIZE: 'normalize',

	/**
	 * Variable: MAXIMIZE
	 *
	 * Specifies the event name for maximize.
	 */
	MAXIMIZE: 'maximize',

	/**
	 * Variable: HIDE
	 *
	 * Specifies the event name for hide.
	 */
	HIDE: 'hide',

	/**
	 * Variable: SHOW
	 *
	 * Specifies the event name for show.
	 */
	SHOW: 'show',

	/**
	 * Variable: CLOSE
	 *
	 * Specifies the event name for close.
	 */
	CLOSE: 'close',

	/**
	 * Variable: DESTROY
	 *
	 * Specifies the event name for destroy.
	 */
	DESTROY: 'destroy',

	/**
	 * Variable: REFRESH
	 *
	 * Specifies the event name for refresh.
	 */
	REFRESH: 'refresh',

	/**
	 * Variable: SIZE
	 *
	 * Specifies the event name for size.
	 */
	SIZE: 'size',
	
	/**
	 * Variable: SELECT
	 *
	 * Specifies the event name for select.
	 */
	SELECT: 'select',

	/**
	 * Variable: FIRED
	 *
	 * Specifies the event name for fired.
	 */
	FIRED: 'fired',

	/**
	 * Variable: FIRE_MOUSE_EVENT
	 *
	 * Specifies the event name for fireMouseEvent.
	 */
	FIRE_MOUSE_EVENT: 'fireMouseEvent',

	/**
	 * Variable: GESTURE
	 *
	 * Specifies the event name for gesture.
	 */
	GESTURE: 'gesture',

	/**
	 * Variable: TAP_AND_HOLD
	 *
	 * Specifies the event name for tapAndHold.
	 */
	TAP_AND_HOLD: 'tapAndHold',

	/**
	 * Variable: GET
	 *
	 * Specifies the event name for get.
	 */
	GET: 'get',

	/**
	 * Variable: RECEIVE
	 *
	 * Specifies the event name for receive.
	 */
	RECEIVE: 'receive',

	/**
	 * Variable: CONNECT
	 *
	 * Specifies the event name for connect.
	 */
	CONNECT: 'connect',

	/**
	 * Variable: DISCONNECT
	 *
	 * Specifies the event name for disconnect.
	 */
	DISCONNECT: 'disconnect',

	/**
	 * Variable: SUSPEND
	 *
	 * Specifies the event name for suspend.
	 */
	SUSPEND: 'suspend',

	/**
	 * Variable: RESUME
	 *
	 * Specifies the event name for suspend.
	 */
	RESUME: 'resume',

	/**
	 * Variable: MARK
	 *
	 * Specifies the event name for mark.
	 */
	MARK: 'mark',

	/**
	 * Variable: ROOT
	 *
	 * Specifies the event name for root.
	 */
	ROOT: 'root',

	/**
	 * Variable: POST
	 *
	 * Specifies the event name for post.
	 */
	POST: 'post',

	/**
	 * Variable: OPEN
	 *
	 * Specifies the event name for open.
	 */
	OPEN: 'open',

	/**
	 * Variable: SAVE
	 *
	 * Specifies the event name for open.
	 */
	SAVE: 'save',

	/**
	 * Variable: BEFORE_ADD_VERTEX
	 *
	 * Specifies the event name for beforeAddVertex.
	 */
	BEFORE_ADD_VERTEX: 'beforeAddVertex',

	/**
	 * Variable: ADD_VERTEX
	 *
	 * Specifies the event name for addVertex.
	 */
	ADD_VERTEX: 'addVertex',

	/**
	 * Variable: AFTER_ADD_VERTEX
	 *
	 * Specifies the event name for afterAddVertex.
	 */
	AFTER_ADD_VERTEX: 'afterAddVertex',

	/**
	 * Variable: DONE
	 *
	 * Specifies the event name for done.
	 */
	DONE: 'done',

	/**
	 * Variable: EXECUTE
	 *
	 * Specifies the event name for execute.
	 */
	EXECUTE: 'execute',

	/**
	 * Variable: EXECUTED
	 *
	 * Specifies the event name for executed.
	 */
	EXECUTED: 'executed',

	/**
	 * Variable: BEGIN_UPDATE
	 *
	 * Specifies the event name for beginUpdate.
	 */
	BEGIN_UPDATE: 'beginUpdate',

	/**
	 * Variable: START_EDIT
	 *
	 * Specifies the event name for startEdit.
	 */
	START_EDIT: 'startEdit',

	/**
	 * Variable: END_UPDATE
	 *
	 * Specifies the event name for endUpdate.
	 */
	END_UPDATE: 'endUpdate',

	/**
	 * Variable: END_EDIT
	 *
	 * Specifies the event name for endEdit.
	 */
	END_EDIT: 'endEdit',

	/**
	 * Variable: BEFORE_UNDO
	 *
	 * Specifies the event name for beforeUndo.
	 */
	BEFORE_UNDO: 'beforeUndo',

	/**
	 * Variable: UNDO
	 *
	 * Specifies the event name for undo.
	 */
	UNDO: 'undo',

	/**
	 * Variable: REDO
	 *
	 * Specifies the event name for redo.
	 */
	REDO: 'redo',

	/**
	 * Variable: CHANGE
	 *
	 * Specifies the event name for change.
	 */
	CHANGE: 'change',

	/**
	 * Variable: NOTIFY
	 *
	 * Specifies the event name for notify.
	 */
	NOTIFY: 'notify',

	/**
	 * Variable: LAYOUT_CELLS
	 *
	 * Specifies the event name for layoutCells.
	 */
	LAYOUT_CELLS: 'layoutCells',

	/**
	 * Variable: CLICK
	 *
	 * Specifies the event name for click.
	 */
	CLICK: 'click',

	/**
	 * Variable: SCALE
	 *
	 * Specifies the event name for scale.
	 */
	SCALE: 'scale',

	/**
	 * Variable: TRANSLATE
	 *
	 * Specifies the event name for translate.
	 */
	TRANSLATE: 'translate',

	/**
	 * Variable: SCALE_AND_TRANSLATE
	 *
	 * Specifies the event name for scaleAndTranslate.
	 */
	SCALE_AND_TRANSLATE: 'scaleAndTranslate',

	/**
	 * Variable: UP
	 *
	 * Specifies the event name for up.
	 */
	UP: 'up',

	/**
	 * Variable: DOWN
	 *
	 * Specifies the event name for down.
	 */
	DOWN: 'down',

	/**
	 * Variable: ADD
	 *
	 * Specifies the event name for add.
	 */
	ADD: 'add',

	/**
	 * Variable: REMOVE
	 *
	 * Specifies the event name for remove.
	 */
	REMOVE: 'remove',
	
	/**
	 * Variable: CLEAR
	 *
	 * Specifies the event name for clear.
	 */
	CLEAR: 'clear',

	/**
	 * Variable: ADD_CELLS
	 *
	 * Specifies the event name for addCells.
	 */
	ADD_CELLS: 'addCells',

	/**
	 * Variable: CELLS_ADDED
	 *
	 * Specifies the event name for cellsAdded.
	 */
	CELLS_ADDED: 'cellsAdded',

	/**
	 * Variable: MOVE_CELLS
	 *
	 * Specifies the event name for moveCells.
	 */
	MOVE_CELLS: 'moveCells',

	/**
	 * Variable: CELLS_MOVED
	 *
	 * Specifies the event name for cellsMoved.
	 */
	CELLS_MOVED: 'cellsMoved',

	/**
	 * Variable: RESIZE_CELLS
	 *
	 * Specifies the event name for resizeCells.
	 */
	RESIZE_CELLS: 'resizeCells',

	/**
	 * Variable: CELLS_RESIZED
	 *
	 * Specifies the event name for cellsResized.
	 */
	CELLS_RESIZED: 'cellsResized',

	/**
	 * Variable: TOGGLE_CELLS
	 *
	 * Specifies the event name for toggleCells.
	 */
	TOGGLE_CELLS: 'toggleCells',

	/**
	 * Variable: CELLS_TOGGLED
	 *
	 * Specifies the event name for cellsToggled.
	 */
	CELLS_TOGGLED: 'cellsToggled',

	/**
	 * Variable: ORDER_CELLS
	 *
	 * Specifies the event name for orderCells.
	 */
	ORDER_CELLS: 'orderCells',

	/**
	 * Variable: CELLS_ORDERED
	 *
	 * Specifies the event name for cellsOrdered.
	 */
	CELLS_ORDERED: 'cellsOrdered',

	/**
	 * Variable: REMOVE_CELLS
	 *
	 * Specifies the event name for removeCells.
	 */
	REMOVE_CELLS: 'removeCells',

	/**
	 * Variable: CELLS_REMOVED
	 *
	 * Specifies the event name for cellsRemoved.
	 */
	CELLS_REMOVED: 'cellsRemoved',

	/**
	 * Variable: GROUP_CELLS
	 *
	 * Specifies the event name for groupCells.
	 */
	GROUP_CELLS: 'groupCells',

	/**
	 * Variable: UNGROUP_CELLS
	 *
	 * Specifies the event name for ungroupCells.
	 */
	UNGROUP_CELLS: 'ungroupCells',

	/**
	 * Variable: REMOVE_CELLS_FROM_PARENT
	 *
	 * Specifies the event name for removeCellsFromParent.
	 */
	REMOVE_CELLS_FROM_PARENT: 'removeCellsFromParent',

	/**
	 * Variable: FOLD_CELLS
	 *
	 * Specifies the event name for foldCells.
	 */
	FOLD_CELLS: 'foldCells',

	/**
	 * Variable: CELLS_FOLDED
	 *
	 * Specifies the event name for cellsFolded.
	 */
	CELLS_FOLDED: 'cellsFolded',

	/**
	 * Variable: ALIGN_CELLS
	 *
	 * Specifies the event name for alignCells.
	 */
	ALIGN_CELLS: 'alignCells',

	/**
	 * Variable: LABEL_CHANGED
	 *
	 * Specifies the event name for labelChanged.
	 */
	LABEL_CHANGED: 'labelChanged',

	/**
	 * Variable: CONNECT_CELL
	 *
	 * Specifies the event name for connectCell.
	 */
	CONNECT_CELL: 'connectCell',

	/**
	 * Variable: CELL_CONNECTED
	 *
	 * Specifies the event name for cellConnected.
	 */
	CELL_CONNECTED: 'cellConnected',

	/**
	 * Variable: SPLIT_EDGE
	 *
	 * Specifies the event name for splitEdge.
	 */
	SPLIT_EDGE: 'splitEdge',

	/**
	 * Variable: FLIP_EDGE
	 *
	 * Specifies the event name for flipEdge.
	 */
	FLIP_EDGE: 'flipEdge',

	/**
	 * Variable: START_EDITING
	 *
	 * Specifies the event name for startEditing.
	 */
	START_EDITING: 'startEditing',

	/**
	 * Variable: EDITING_STARTED
	 *
	 * Specifies the event name for editingStarted.
	 */
	EDITING_STARTED: 'editingStarted',

	/**
	 * Variable: EDITING_STOPPED
	 *
	 * Specifies the event name for editingStopped.
	 */
	EDITING_STOPPED: 'editingStopped',

	/**
	 * Variable: ADD_OVERLAY
	 *
	 * Specifies the event name for addOverlay.
	 */
	ADD_OVERLAY: 'addOverlay',

	/**
	 * Variable: REMOVE_OVERLAY
	 *
	 * Specifies the event name for removeOverlay.
	 */
	REMOVE_OVERLAY: 'removeOverlay',

	/**
	 * Variable: UPDATE_CELL_SIZE
	 *
	 * Specifies the event name for updateCellSize.
	 */
	UPDATE_CELL_SIZE: 'updateCellSize',

	/**
	 * Variable: ESCAPE
	 *
	 * Specifies the event name for escape.
	 */
	ESCAPE: 'escape',

	/**
	 * Variable: DOUBLE_CLICK
	 *
	 * Specifies the event name for doubleClick.
	 */
	DOUBLE_CLICK: 'doubleClick',

	/**
	 * Variable: START
	 *
	 * Specifies the event name for start.
	 */
	START: 'start',

	/**
	 * Variable: RESET
	 *
	 * Specifies the event name for reset.
	 */
	RESET: 'reset'

};
/**
 * Copyright (c) 2006-2020, JGraph Ltd
 * Copyright (c) 2006-2020, draw.io AG
 */
/**
 * Class: mxXmlRequest
 * 
 * XML HTTP request wrapper. See also: ,  and
 * . This class provides a cross-browser abstraction for Ajax
 * requests.
 * 
 * Encoding:
 * 
 * For encoding parameter values, the built-in encodeURIComponent JavaScript
 * method must be used. For automatic encoding of post data in  the
 *  switch can be set to true (default). The encoding
 * will be carried out using the conte type of the page. That is, the page
 * containting the editor should contain a meta tag in the header, eg.
 * 
 * 
 * Example:
 * 
 * (code)
 * var onload = function(req)
 * {
 *   mxUtils.alert(req.getDocumentElement());
 * }
 * 
 * var onerror = function(req)
 * {
 *   mxUtils.alert('Error');
 * }
 * new mxXmlRequest(url, 'key=value').send(onload, onerror);
 * (end)
 * 
 * Sends an asynchronous POST request to the specified URL.
 * 
 * Example:
 * 
 * (code)
 * var req = new mxXmlRequest(url, 'key=value', 'POST', false);
 * req.send();
 * mxUtils.alert(req.getDocumentElement());
 * (end)
 * 
 * Sends a synchronous POST request to the specified URL.
 * 
 * Example:
 * 
 * (code)
 * var encoder = new mxCodec();
 * var result = encoder.encode(graph.getModel());
 * var xml = encodeURIComponent(mxUtils.getXml(result));
 * new mxXmlRequest(url, 'xml='+xml).send();
 * (end)
 * 
 * Sends an encoded graph model to the specified URL using xml as the
 * parameter name. The parameter can then be retrieved in C# as follows:
 * 
 * (code)
 * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]);
 * (end)
 * 
 * Or in Java as follows:
 * 
 * (code)
 * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "
");
 * (end)
 *
 * Note that the linefeeds should only be replaced if the XML is
 * processed in Java, for example when creating an image.
 * 
 * Constructor: mxXmlRequest
 * 
 * Constructs an XML HTTP request.
 * 
 * Parameters:
 * 
 * url - Target URL of the request.
 * params - Form encoded parameters to send with a POST request.
 * method - String that specifies the request method. Possible values are
 * POST and GET. Default is POST.
 * async - Boolean specifying if an asynchronous request should be used.
 * Default is true.
 * username - String specifying the username to be used for the request.
 * password - String specifying the password to be used for the request.
 */
function mxXmlRequest(url, params, method, async, username, password)
{
	this.url = url;
	this.params = params;
	this.method = method || 'POST';
	this.async = (async != null) ? async : true;
	this.username = username;
	this.password = password;
};

/**
 * Variable: url
 * 
 * Holds the target URL of the request.
 */
mxXmlRequest.prototype.url = null;

/**
 * Variable: params
 * 
 * Holds the form encoded data for the POST request.
 */
mxXmlRequest.prototype.params = null;

/**
 * Variable: method
 * 
 * Specifies the request method. Possible values are POST and GET. Default
 * is POST.
 */
mxXmlRequest.prototype.method = null;

/**
 * Variable: async
 * 
 * Boolean indicating if the request is asynchronous.
 */
mxXmlRequest.prototype.async = null;

/**
 * Variable: binary
 * 
 * Boolean indicating if the request is binary. This option is ignored in IE.
 * In all other browsers the requested mime type is set to
 * text/plain; charset=x-user-defined. Default is false.
 */
mxXmlRequest.prototype.binary = false;

/**
 * Variable: withCredentials
 * 
 * Specifies if withCredentials should be used in HTML5-compliant browsers. Default is
 * false.
 */
mxXmlRequest.prototype.withCredentials = false;

/**
 * Variable: username
 * 
 * Specifies the username to be used for authentication.
 */
mxXmlRequest.prototype.username = null;

/**
 * Variable: password
 * 
 * Specifies the password to be used for authentication.
 */
mxXmlRequest.prototype.password = null;

/**
 * Variable: request
 * 
 * Holds the inner, browser-specific request object.
 */
mxXmlRequest.prototype.request = null;

/**
 * Variable: decodeSimulateValues
 * 
 * Specifies if request values should be decoded as URIs before setting the
 * textarea value in . Defaults to false for backwards compatibility,
 * to avoid another decode on the server this should be set to true.
 */
mxXmlRequest.prototype.decodeSimulateValues = false;

/**
 * Function: isBinary
 * 
 * Returns .
 */
mxXmlRequest.prototype.isBinary = function()
{
	return this.binary;
};

/**
 * Function: setBinary
 * 
 * Sets .
 */
mxXmlRequest.prototype.setBinary = function(value)
{
	this.binary = value;
};

/**
 * Function: getText
 * 
 * Returns the response as a string.
 */
mxXmlRequest.prototype.getText = function()
{
	return this.request.responseText;
};

/**
 * Function: isReady
 * 
 * Returns true if the response is ready.
 */
mxXmlRequest.prototype.isReady = function()
{
	return this.request.readyState == 4;
};

/**
 * Function: getDocumentElement
 * 
 * Returns the document element of the response XML document.
 */
mxXmlRequest.prototype.getDocumentElement = function()
{
	var doc = this.getXml();
	
	if (doc != null)
	{
		return doc.documentElement;
	}
	
	return null;
};

/**
 * Function: getXml
 * 
 * Returns the response as an XML document. Use  to get
 * the document element of the XML document.
 */
mxXmlRequest.prototype.getXml = function()
{
	var xml = this.request.responseXML;
	
	// Handles missing response headers in IE, the first condition handles
	// the case where responseXML is there, but using its nodes leads to
	// type errors in the mxCellCodec when putting the nodes into a new
	// document. This happens in IE9 standards mode and with XML user
	// objects only, as they are used directly as values in cells.
	if (document.documentMode >= 9 || xml == null || xml.documentElement == null)
	{
		xml = mxUtils.parseXml(this.request.responseText);
	}
	
	return xml;
};

/**
 * Function: getText
 * 
 * Returns the response as a string.
 */
mxXmlRequest.prototype.getText = function()
{
	return this.request.responseText;
};

/**
 * Function: getStatus
 * 
 * Returns the status as a number, eg. 404 for "Not found" or 200 for "OK".
 * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought.
 */
mxXmlRequest.prototype.getStatus = function()
{
	return (this.request != null) ? this.request.status : null;
};

/**
 * Function: create
 * 
 * Creates and returns the inner  object.
 */
mxXmlRequest.prototype.create = function()
{
	if (window.XMLHttpRequest)
	{
		return function()
		{
			var req = new XMLHttpRequest();
			
			// TODO: Check for overrideMimeType required here?
			if (this.isBinary() && req.overrideMimeType)
			{
				req.overrideMimeType('text/plain; charset=x-user-defined');
			}

			return req;
		};
	}
	else if (typeof(ActiveXObject) != 'undefined')
	{
		return function()
		{
			// TODO: Implement binary option
			return new ActiveXObject('Microsoft.XMLHTTP');
		};
	}
}();

/**
 * Function: send
 * 
 * Send the  to the target URL using the specified functions to
 * process the response asychronously.
 * 
 * Note: Due to technical limitations, onerror is currently ignored.
 * 
 * Parameters:
 * 
 * onload - Function to be invoked if a successful response was received.
 * onerror - Function to be called on any error.
 * timeout - Optional timeout in ms before calling ontimeout.
 * ontimeout - Optional function to execute on timeout.
 */
mxXmlRequest.prototype.send = function(onload, onerror, timeout, ontimeout)
{
	this.request = this.create();
	
	if (this.request != null)
	{
		if (onload != null)
		{
			this.request.onreadystatechange = mxUtils.bind(this, function()
			{
				if (this.isReady())
				{
					onload(this);
					this.request.onreadystatechange = null;
				}
			});
		}

		this.request.open(this.method, this.url, this.async,
			this.username, this.password);
		this.setRequestHeaders(this.request, this.params);
		
		if (window.XMLHttpRequest && this.withCredentials)
		{
			this.request.withCredentials = 'true';
		}
		
		if (!mxClient.IS_QUIRKS && (document.documentMode == null || document.documentMode > 9) &&
			window.XMLHttpRequest && timeout != null && ontimeout != null)
		{
			this.request.timeout = timeout;
			this.request.ontimeout = ontimeout;
		}
				
		this.request.send(this.params);
	}
};

/**
 * Function: setRequestHeaders
 * 
 * Sets the headers for the given request and parameters. This sets the
 * content-type to application/x-www-form-urlencoded if any params exist.
 * 
 * Example:
 * 
 * (code)
 * request.setRequestHeaders = function(request, params)
 * {
 *   if (params != null)
 *   {
 *     request.setRequestHeader('Content-Type',
 *             'multipart/form-data');
 *     request.setRequestHeader('Content-Length',
 *             params.length);
 *   }
 * };
 * (end)
 * 
 * Use the code above before calling  if you require a
 * multipart/form-data request.   
 */
mxXmlRequest.prototype.setRequestHeaders = function(request, params)
{
	if (params != null)
	{
		request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
	}
};

/**
 * Function: simulate
 * 
 * Creates and posts a request to the given target URL using a dynamically
 * created form inside the given document.
 * 
 * Parameters:
 * 
 * docs - Document that contains the form element.
 * target - Target to send the form result to.
 */
mxXmlRequest.prototype.simulate = function(doc, target)
{
	doc = doc || document;
	var old = null;

	if (doc == document)
	{
		old = window.onbeforeunload;		
		window.onbeforeunload = null;
	}
			
	var form = doc.createElement('form');
	form.setAttribute('method', this.method);
	form.setAttribute('action', this.url);

	if (target != null)
	{
		form.setAttribute('target', target);
	}

	form.style.display = 'none';
	form.style.visibility = 'hidden';
	
	var pars = (this.params.indexOf('&') > 0) ?
		this.params.split('&') :
		this.params.split();

	// Adds the parameters as textareas to the form
	for (var i=0; i 0)
		{
			var name = pars[i].substring(0, pos);
			var value = pars[i].substring(pos+1);
			
			if (this.decodeSimulateValues)
			{
				value = decodeURIComponent(value);
			}
			
			var textarea = doc.createElement('textarea');
			textarea.setAttribute('wrap', 'off');
			textarea.setAttribute('name', name);
			mxUtils.write(textarea, value);
			form.appendChild(textarea);
		}
	}
	
	doc.body.appendChild(form);
	form.submit();
	
	if (form.parentNode != null)
	{
		form.parentNode.removeChild(form);
	}

	if (old != null)
	{		
		window.onbeforeunload = old;
	}
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxClipboard =
{
	/**
	 * Class: mxClipboard
	 * 
	 * Singleton that implements a clipboard for graph cells.
	 *
	 * Example:
	 * 
	 * (code)
	 * mxClipboard.copy(graph);
	 * mxClipboard.paste(graph2);
	 * (end)
	 *
	 * This copies the selection cells from the graph to the clipboard and
	 * pastes them into graph2.
	 * 
	 * For fine-grained control of the clipboard data the 
	 * and  functions can be overridden.
	 * 
	 * To restore previous parents for pasted cells, the implementation for
	 *  and  can be changed as follows.
	 * 
	 * (code)
	 * mxClipboard.copy = function(graph, cells)
	 * {
	 *   cells = cells || graph.getSelectionCells();
	 *   var result = graph.getExportableCells(cells);
	 *   
	 *   mxClipboard.parents = new Object();
	 *   
	 *   for (var i = 0; i < result.length; i++)
	 *   {
	 *     mxClipboard.parents[i] = graph.model.getParent(cells[i]);
	 *   }
	 *   
	 *   mxClipboard.insertCount = 1;
	 *   mxClipboard.setCells(graph.cloneCells(result));
	 *   
	 *   return result;
	 * };
	 * 
	 * mxClipboard.paste = function(graph)
	 * {
	 *   if (!mxClipboard.isEmpty())
	 *   {
	 *     var cells = graph.getImportableCells(mxClipboard.getCells());
	 *     var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
	 *     var parent = graph.getDefaultParent();
	 *     
	 *     graph.model.beginUpdate();
	 *     try
	 *     {
	 *       for (var i = 0; i < cells.length; i++)
	 *       {
	 *         var tmp = (mxClipboard.parents != null && graph.model.contains(mxClipboard.parents[i])) ?
	 *              mxClipboard.parents[i] : parent;
	 *         cells[i] = graph.importCells([cells[i]], delta, delta, tmp)[0];
	 *       }
	 *     }
	 *     finally
	 *     {
	 *       graph.model.endUpdate();
	 *     }
	 *     
	 *     // Increments the counter and selects the inserted cells
	 *     mxClipboard.insertCount++;
	 *     graph.setSelectionCells(cells);
	 *   }
	 * };
	 * (end)
	 * 
	 * Variable: STEPSIZE
	 * 
	 * Defines the step size to offset the cells after each paste operation.
	 * Default is 10.
	 */
	STEPSIZE: 10,

	/**
	 * Variable: insertCount
	 * 
	 * Counts the number of times the clipboard data has been inserted.
	 */
	insertCount: 1,

	/**
	 * Variable: cells
	 * 
	 * Holds the array of  currently in the clipboard.
	 */
	cells: null,

	/**
	 * Function: setCells
	 * 
	 * Sets the cells in the clipboard. Fires a  event.
	 */
	setCells: function(cells)
	{
		mxClipboard.cells = cells;
	},

	/**
	 * Function: getCells
	 * 
	 * Returns  the cells in the clipboard.
	 */
	getCells: function()
	{
		return mxClipboard.cells;
	},
	
	/**
	 * Function: isEmpty
	 * 
	 * Returns true if the clipboard currently has not data stored.
	 */
	isEmpty: function()
	{
		return mxClipboard.getCells() == null;
	},
	
	/**
	 * Function: cut
	 * 
	 * Cuts the given array of  from the specified graph.
	 * If cells is null then the selection cells of the graph will
	 * be used. Returns the cells that have been cut from the graph.
	 *
	 * Parameters:
	 * 
	 * graph -  that contains the cells to be cut.
	 * cells - Optional array of  to be cut.
	 */
	cut: function(graph, cells)
	{
		cells = mxClipboard.copy(graph, cells);
		mxClipboard.insertCount = 0;
		mxClipboard.removeCells(graph, cells);
		
		return cells;
	},

	/**
	 * Function: removeCells
	 * 
	 * Hook to remove the given cells from the given graph after
	 * a cut operation.
	 *
	 * Parameters:
	 * 
	 * graph -  that contains the cells to be cut.
	 * cells - Array of  to be cut.
	 */
	removeCells: function(graph, cells)
	{
		graph.removeCells(cells);
	},

	/**
	 * Function: copy
	 * 
	 * Copies the given array of  from the specified
	 * graph to . Returns the original array of cells that has
	 * been cloned. Descendants of cells in the array are ignored.
	 * 
	 * Parameters:
	 * 
	 * graph -  that contains the cells to be copied.
	 * cells - Optional array of  to be copied.
	 */
	copy: function(graph, cells)
	{
		cells = cells || graph.getSelectionCells();
		var result = graph.getExportableCells(graph.model.getTopmostCells(cells));
		mxClipboard.insertCount = 1;
		mxClipboard.setCells(graph.cloneCells(result));

		return result;
	},

	/**
	 * Function: paste
	 * 
	 * Pastes the  into the specified graph restoring
	 * the relation to , if possible. If the parents
	 * are no longer in the graph or invisible then the
	 * cells are added to the graph's default or into the
	 * swimlane under the cell's new location if one exists.
	 * The cells are added to the graph using 
	 * and returned.
	 * 
	 * Parameters:
	 * 
	 * graph -  to paste the  into.
	 */
	paste: function(graph)
	{
		var cells = null;
		
		if (!mxClipboard.isEmpty())
		{
			cells = graph.getImportableCells(mxClipboard.getCells());
			var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
			var parent = graph.getDefaultParent();
			cells = graph.importCells(cells, delta, delta, parent);
			
			// Increments the counter and selects the inserted cells
			mxClipboard.insertCount++;
			graph.setSelectionCells(cells);
		}
		
		return cells;
	}

};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxWindow
 * 
 * Basic window inside a document.
 * 
 * Examples:
 * 
 * Creating a simple window.
 *
 * (code)
 * var tb = document.createElement('div');
 * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true);
 * wnd.setVisible(true); 
 * (end)
 *
 * Creating a window that contains an iframe. 
 * 
 * (code)
 * var frame = document.createElement('iframe');
 * frame.setAttribute('width', '192px');
 * frame.setAttribute('height', '172px');
 * frame.setAttribute('src', 'http://www.example.com/');
 * frame.style.backgroundColor = 'white';
 * 
 * var w = document.body.clientWidth;
 * var h = (document.body.clientHeight || document.documentElement.clientHeight);
 * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200);
 * wnd.setVisible(true);
 * (end)
 * 
 * To limit the movement of a window, eg. to keep it from being moved beyond
 * the top, left corner the following method can be overridden (recommended):
 * 
 * (code)
 * wnd.setLocation = function(x, y)
 * {
 *   x = Math.max(0, x);
 *   y = Math.max(0, y);
 *   mxWindow.prototype.setLocation.apply(this, arguments);
 * };
 * (end)
 * 
 * Or the following event handler can be used:
 * 
 * (code)
 * wnd.addListener(mxEvent.MOVE, function(e)
 * {
 *   wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY()));
 * });
 * (end)
 * 
 * To keep a window inside the current window:
 * 
 * (code)
 * mxEvent.addListener(window, 'resize', mxUtils.bind(this, function()
 * {
 *   var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
 *   var ih = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
 *   
 *   var x = this.window.getX();
 *   var y = this.window.getY();
 *   
 *   if (x + this.window.table.clientWidth > iw)
 *   {
 *     x = Math.max(0, iw - this.window.table.clientWidth);
 *   }
 *   
 *   if (y + this.window.table.clientHeight > ih)
 *   {
 *     y = Math.max(0, ih - this.window.table.clientHeight);
 *   }
 *   
 *   if (this.window.getX() != x || this.window.getY() != y)
 *   {
 *     this.window.setLocation(x, y);
 *   }
 * }));
 * (end)
 *
 * Event: mxEvent.MOVE_START
 *
 * Fires before the window is moved. The event property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.MOVE
 *
 * Fires while the window is being moved. The event property
 * contains the corresponding mouse event.
 *
 * Event: mxEvent.MOVE_END
 *
 * Fires after the window is moved. The event property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.RESIZE_START
 *
 * Fires before the window is resized. The event property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.RESIZE
 *
 * Fires while the window is being resized. The event property
 * contains the corresponding mouse event.
 *
 * Event: mxEvent.RESIZE_END
 *
 * Fires after the window is resized. The event property contains
 * the corresponding mouse event.
 *
 * Event: mxEvent.MAXIMIZE
 * 
 * Fires after the window is maximized. The event property
 * contains the corresponding mouse event.
 * 
 * Event: mxEvent.MINIMIZE
 * 
 * Fires after the window is minimized. The event property
 * contains the corresponding mouse event.
 * 
 * Event: mxEvent.NORMALIZE
 * 
 * Fires after the window is normalized, that is, it returned from
 * maximized or minimized state. The event property contains the
 * corresponding mouse event.
 *  
 * Event: mxEvent.ACTIVATE
 * 
 * Fires after a window is activated. The previousWindow property
 * contains the previous window. The event sender is the active window.
 * 
 * Event: mxEvent.SHOW
 * 
 * Fires after the window is shown. This event has no properties.
 * 
 * Event: mxEvent.HIDE
 * 
 * Fires after the window is hidden. This event has no properties.
 * 
 * Event: mxEvent.CLOSE
 * 
 * Fires before the window is closed. The event property contains
 * the corresponding mouse event.
 * 
 * Event: mxEvent.DESTROY
 * 
 * Fires before the window is destroyed. This event has no properties.
 * 
 * Constructor: mxWindow
 * 
 * Constructs a new window with the given dimension and title to display
 * the specified content. The window elements use the given style as a
 * prefix for the classnames of the respective window elements, namely,
 * the window title and window pane. The respective postfixes are appended
 * to the given stylename as follows:
 * 
 *   style - Base style for the window.
 *   style+Title - Style for the window title.
 *   style+Pane - Style for the window pane.
 * 
 * The default value for style is mxWindow, resulting in the following
 * classnames for the window elements: mxWindow, mxWindowTitle and
 * mxWindowPane.
 * 
 * If replaceNode is given then the window replaces the given DOM node in
 * the document.
 * 
 * Parameters:
 * 
 * title - String that represents the title of the new window.
 * content - DOM node that is used as the window content.
 * x - X-coordinate of the window location.
 * y - Y-coordinate of the window location.
 * width - Width of the window.
 * height - Optional height of the window. Default is to match the height
 * of the content at the specified width.
 * minimizable - Optional boolean indicating if the window is minimizable.
 * Default is true.
 * movable - Optional boolean indicating if the window is movable. Default
 * is true.
 * replaceNode - Optional DOM node that the window should replace.
 * style - Optional base classname for the window elements. Default is
 * mxWindow.
 */
function mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style)
{
	if (content != null)
	{
		minimizable = (minimizable != null) ? minimizable : true;
		this.content = content;
		this.init(x, y, width, height, style);
		
		this.installMaximizeHandler();
		this.installMinimizeHandler();
		this.installCloseHandler();
		this.setMinimizable(minimizable);
		this.setTitle(title);
		
		if (movable == null || movable)
		{
			this.installMoveHandler();
		}

		if (replaceNode != null && replaceNode.parentNode != null)
		{
			replaceNode.parentNode.replaceChild(this.div, replaceNode);
		}
		else
		{
			document.body.appendChild(this.div);
		}
	}
};

/**
 * Extends mxEventSource.
 */
mxWindow.prototype = new mxEventSource();
mxWindow.prototype.constructor = mxWindow;

/**
 * Variable: closeImage
 * 
 * URL of the image to be used for the close icon in the titlebar.
 */
mxWindow.prototype.closeImage = mxClient.imageBasePath + '/close.gif';

/**
 * Variable: minimizeImage
 * 
 * URL of the image to be used for the minimize icon in the titlebar.
 */
mxWindow.prototype.minimizeImage = mxClient.imageBasePath + '/minimize.gif';
	
/**
 * Variable: normalizeImage
 * 
 * URL of the image to be used for the normalize icon in the titlebar.
 */
mxWindow.prototype.normalizeImage = mxClient.imageBasePath + '/normalize.gif';
	
/**
 * Variable: maximizeImage
 * 
 * URL of the image to be used for the maximize icon in the titlebar.
 */
mxWindow.prototype.maximizeImage = mxClient.imageBasePath + '/maximize.gif';

/**
 * Variable: normalizeImage
 * 
 * URL of the image to be used for the resize icon.
 */
mxWindow.prototype.resizeImage = mxClient.imageBasePath + '/resize.gif';

/**
 * Variable: visible
 * 
 * Boolean flag that represents the visible state of the window.
 */
mxWindow.prototype.visible = false;

/**
 * Variable: minimumSize
 * 
 *  that specifies the minimum width and height of the window.
 * Default is (50, 40).
 */
mxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);

/**
 * Variable: destroyOnClose
 * 
 * Specifies if the window should be destroyed when it is closed. If this
 * is false then the window is hidden using . Default is true.
 */
mxWindow.prototype.destroyOnClose = true;

/**
 * Variable: contentHeightCorrection
 * 
 * Defines the correction factor for computing the height of the contentWrapper.
 * Default is 6 for IE 7/8 standards mode and 2 for all other browsers and modes.
 */
mxWindow.prototype.contentHeightCorrection = (document.documentMode == 8 || document.documentMode == 7) ? 6 : 2;

/**
 * Variable: title
 * 
 * Reference to the DOM node (TD) that contains the title.
 */
mxWindow.prototype.title = null;

/**
 * Variable: content
 * 
 * Reference to the DOM node that represents the window content.
 */
mxWindow.prototype.content = null;

/**
 * Function: init
 * 
 * Initializes the DOM tree that represents the window.
 */
mxWindow.prototype.init = function(x, y, width, height, style)
{
	style = (style != null) ? style : 'mxWindow';
	
	this.div = document.createElement('div');
	this.div.className = style;

	this.div.style.left = x + 'px';
	this.div.style.top = y + 'px';
	this.table = document.createElement('table');
	this.table.className = style;

	// Disables built-in pan and zoom in IE10 and later
	if (mxClient.IS_POINTER)
	{
		this.div.style.touchAction = 'none';
	}
	
	// Workaround for table size problems in FF
	if (width != null)
	{
		if (!mxClient.IS_QUIRKS)
		{
			this.div.style.width = width + 'px'; 
		}
		
		this.table.style.width = width + 'px';
	} 
	
	if (height != null)
	{
		if (!mxClient.IS_QUIRKS)
		{
			this.div.style.height = height + 'px';
		}
		
		this.table.style.height = height + 'px';
	}		
	
	// Creates title row
	var tbody = document.createElement('tbody');
	var tr = document.createElement('tr');
	
	this.title = document.createElement('td');
	this.title.className = style + 'Title';
	
	this.buttons = document.createElement('div');
	this.buttons.style.position = 'absolute';
	this.buttons.style.display = 'inline-block';
	this.buttons.style.right = '4px';
	this.buttons.style.top = '5px';
	this.title.appendChild(this.buttons);
	
	tr.appendChild(this.title);
	tbody.appendChild(tr);
	
	// Creates content row and table cell
	tr = document.createElement('tr');
	this.td = document.createElement('td');
	this.td.className = style + 'Pane';
	
	if (document.documentMode == 7)
	{
		this.td.style.height = '100%';
	}

	this.contentWrapper = document.createElement('div');
	this.contentWrapper.className = style + 'Pane';
	this.contentWrapper.style.width = '100%';
	this.contentWrapper.appendChild(this.content);

	// Workaround for div around div restricts height
	// of inner div if outerdiv has hidden overflow
	if (mxClient.IS_QUIRKS || this.content.nodeName.toUpperCase() != 'DIV')
	{
		this.contentWrapper.style.height = '100%';
	}

	// Puts all content into the DOM
	this.td.appendChild(this.contentWrapper);
	tr.appendChild(this.td);
	tbody.appendChild(tr);
	this.table.appendChild(tbody);
	this.div.appendChild(this.table);
	
	// Puts the window on top of other windows when clicked
	var activator = mxUtils.bind(this, function(evt)
	{
		this.activate();
	});
	
	mxEvent.addGestureListeners(this.title, activator);
	mxEvent.addGestureListeners(this.table, activator);

	this.hide();
};

/**
 * Function: setTitle
 * 
 * Sets the window title to the given string. HTML markup inside the title
 * will be escaped.
 */
mxWindow.prototype.setTitle = function(title)
{
	// Removes all text content nodes (normally just one)
	var child = this.title.firstChild;
	
	while (child != null)
	{
		var next = child.nextSibling;
		
		if (child.nodeType == mxConstants.NODETYPE_TEXT)
		{
			child.parentNode.removeChild(child);
		}
		
		child = next;
	}
	
	mxUtils.write(this.title, title || '');
	this.title.appendChild(this.buttons);
};

/**
 * Function: setScrollable
 * 
 * Sets if the window contents should be scrollable.
 */
mxWindow.prototype.setScrollable = function(scrollable)
{
	// Workaround for hang in Presto 2.5.22 (Opera 10.5)
	if (navigator.userAgent == null ||
		navigator.userAgent.indexOf('Presto/2.5') < 0)
	{
		if (scrollable)
		{
			this.contentWrapper.style.overflow = 'auto';
		}
		else
		{
			this.contentWrapper.style.overflow = 'hidden';
		}
	}
};

/**
 * Function: activate
 * 
 * Puts the window on top of all other windows.
 */
mxWindow.prototype.activate = function()
{
	if (mxWindow.activeWindow != this)
	{
		var style = mxUtils.getCurrentStyle(this.getElement());
		var index = (style != null) ? style.zIndex : 3;

		if (mxWindow.activeWindow)
		{
			var elt = mxWindow.activeWindow.getElement();
			
			if (elt != null && elt.style != null)
			{
				elt.style.zIndex = index;
			}
		}
		
		var previousWindow = mxWindow.activeWindow;
		this.getElement().style.zIndex = parseInt(index) + 1;
		mxWindow.activeWindow = this;
		
		this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow));
	}
};

/**
 * Function: getElement
 * 
 * Returuns the outermost DOM node that makes up the window.
 */
mxWindow.prototype.getElement = function()
{
	return this.div;
};

/**
 * Function: fit
 * 
 * Makes sure the window is inside the client area of the window.
 */
mxWindow.prototype.fit = function()
{
	mxUtils.fit(this.div);
};

/**
 * Function: isResizable
 * 
 * Returns true if the window is resizable.
 */
mxWindow.prototype.isResizable = function()
{
	if (this.resize != null)
	{
		return this.resize.style.display != 'none';
	}
	
	return false;
};

/**
 * Function: setResizable
 * 
 * Sets if the window should be resizable. To avoid interference with some
 * built-in features of IE10 and later, the use of the following code is
 * recommended if there are resizable s in the page:
 * 
 * (code)
 * if (mxClient.IS_POINTER)
 * {
 *   document.body.style.msTouchAction = 'none';
 * }
 * (end)
 */
mxWindow.prototype.setResizable = function(resizable)
{
	if (resizable)
	{
		if (this.resize == null)
		{
			this.resize = document.createElement('img');
			this.resize.style.position = 'absolute';
			this.resize.style.bottom = '2px';
			this.resize.style.right = '2px';

			this.resize.setAttribute('src', this.resizeImage);
			this.resize.style.cursor = 'nw-resize';
			
			var startX = null;
			var startY = null;
			var width = null;
			var height = null;
			
			var start = mxUtils.bind(this, function(evt)
			{
				// LATER: pointerdown starting on border of resize does start
				// the drag operation but does not fire consecutive events via
				// one of the listeners below (does pan instead).
				// Workaround: document.body.style.msTouchAction = 'none'
				this.activate();
				startX = mxEvent.getClientX(evt);
				startY = mxEvent.getClientY(evt);
				width = this.div.offsetWidth;
				height = this.div.offsetHeight;
				
				mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
				this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt));
				mxEvent.consume(evt);
			});

			// Adds a temporary pair of listeners to intercept
			// the gesture event in the document
			var dragHandler = mxUtils.bind(this, function(evt)
			{
				if (startX != null && startY != null)
				{
					var dx = mxEvent.getClientX(evt) - startX;
					var dy = mxEvent.getClientY(evt) - startY;
	
					this.setSize(width + dx, height + dy);
	
					this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt));
					mxEvent.consume(evt);
				}
			});
			
			var dropHandler = mxUtils.bind(this, function(evt)
			{
				if (startX != null && startY != null)
				{
					startX = null;
					startY = null;
					mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
					this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt));
					mxEvent.consume(evt);
				}
			});
			
			mxEvent.addGestureListeners(this.resize, start, dragHandler, dropHandler);
			this.div.appendChild(this.resize);
		}
		else 
		{
			this.resize.style.display = 'inline';
		}
	}
	else if (this.resize != null)
	{
		this.resize.style.display = 'none';
	}
};
	
/**
 * Function: setSize
 * 
 * Sets the size of the window.
 */
mxWindow.prototype.setSize = function(width, height)
{
	width = Math.max(this.minimumSize.width, width);
	height = Math.max(this.minimumSize.height, height);

	// Workaround for table size problems in FF
	if (!mxClient.IS_QUIRKS)
	{
		this.div.style.width =  width + 'px';
		this.div.style.height = height + 'px';
	}
	
	this.table.style.width =  width + 'px';
	this.table.style.height = height + 'px';

	if (!mxClient.IS_QUIRKS)
	{
		this.contentWrapper.style.height = (this.div.offsetHeight -
			this.title.offsetHeight - this.contentHeightCorrection) + 'px';
	}
};
	
/**
 * Function: setMinimizable
 * 
 * Sets if the window is minimizable.
 */
mxWindow.prototype.setMinimizable = function(minimizable)
{
	this.minimize.style.display = (minimizable) ? '' : 'none';
};

/**
 * Function: getMinimumSize
 * 
 * Returns an  that specifies the size for the minimized window.
 * A width or height of 0 means keep the existing width or height. This
 * implementation returns the height of the window title and keeps the width.
 */
mxWindow.prototype.getMinimumSize = function()
{
	return new mxRectangle(0, 0, 0, this.title.offsetHeight);
};

/**
 * Function: installMinimizeHandler
 * 
 * Installs the event listeners required for minimizing the window.
 */
mxWindow.prototype.installMinimizeHandler = function()
{
	this.minimize = document.createElement('img');
	
	this.minimize.setAttribute('src', this.minimizeImage);
	this.minimize.setAttribute('title', 'Minimize');
	this.minimize.style.cursor = 'pointer';
	this.minimize.style.marginLeft = '2px';
	this.minimize.style.display = 'none';
	
	this.buttons.appendChild(this.minimize);
	
	var minimized = false;
	var maxDisplay = null;
	var height = null;

	var funct = mxUtils.bind(this, function(evt)
	{
		this.activate();
		
		if (!minimized)
		{
			minimized = true;
			
			this.minimize.setAttribute('src', this.normalizeImage);
			this.minimize.setAttribute('title', 'Normalize');
			this.contentWrapper.style.display = 'none';
			maxDisplay = this.maximize.style.display;
			
			this.maximize.style.display = 'none';
			height = this.table.style.height;
			
			var minSize = this.getMinimumSize();
			
			if (minSize.height > 0)
			{
				if (!mxClient.IS_QUIRKS)
				{
					this.div.style.height = minSize.height + 'px';
				}
				
				this.table.style.height = minSize.height + 'px';
			}
			
			if (minSize.width > 0)
			{
				if (!mxClient.IS_QUIRKS)
				{
					this.div.style.width = minSize.width + 'px';
				}
				
				this.table.style.width = minSize.width + 'px';
			}
			
			if (this.resize != null)
			{
				this.resize.style.visibility = 'hidden';
			}
			
			this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt));
		}
		else
		{
			minimized = false;
			
			this.minimize.setAttribute('src', this.minimizeImage);
			this.minimize.setAttribute('title', 'Minimize');
			this.contentWrapper.style.display = ''; // default
			this.maximize.style.display = maxDisplay;
			
			if (!mxClient.IS_QUIRKS)
			{
				this.div.style.height = height;
			}
			
			this.table.style.height = height;

			if (this.resize != null)
			{
				this.resize.style.visibility = '';
			}
			
			this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
		}
		
		mxEvent.consume(evt);
	});
	
	mxEvent.addGestureListeners(this.minimize, funct);
};
	
/**
 * Function: setMaximizable
 * 
 * Sets if the window is maximizable.
 */
mxWindow.prototype.setMaximizable = function(maximizable)
{
	this.maximize.style.display = (maximizable) ? '' : 'none';
};

/**
 * Function: installMaximizeHandler
 * 
 * Installs the event listeners required for maximizing the window.
 */
mxWindow.prototype.installMaximizeHandler = function()
{
	this.maximize = document.createElement('img');
	
	this.maximize.setAttribute('src', this.maximizeImage);
	this.maximize.setAttribute('title', 'Maximize');
	this.maximize.style.cursor = 'default';
	this.maximize.style.marginLeft = '2px';
	this.maximize.style.cursor = 'pointer';
	this.maximize.style.display = 'none';
	
	this.buttons.appendChild(this.maximize);
	
	var maximized = false;
	var x = null;
	var y = null;
	var height = null;
	var width = null;
	var minDisplay = null;

	var funct = mxUtils.bind(this, function(evt)
	{
		this.activate();
		
		if (this.maximize.style.display != 'none')
		{
			if (!maximized)
			{
				maximized = true;
				
				this.maximize.setAttribute('src', this.normalizeImage);
				this.maximize.setAttribute('title', 'Normalize');
				this.contentWrapper.style.display = '';
				minDisplay = this.minimize.style.display;
				this.minimize.style.display = 'none';
				
				// Saves window state
				x = parseInt(this.div.style.left);
				y = parseInt(this.div.style.top);
				height = this.table.style.height;
				width = this.table.style.width;

				this.div.style.left = '0px';
				this.div.style.top = '0px';
				var docHeight = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0);

				if (!mxClient.IS_QUIRKS)
				{
					this.div.style.width = (document.body.clientWidth - 2) + 'px';
					this.div.style.height = (docHeight - 2) + 'px';
				}

				this.table.style.width = (document.body.clientWidth - 2) + 'px';
				this.table.style.height = (docHeight - 2) + 'px';
				
				if (this.resize != null)
				{
					this.resize.style.visibility = 'hidden';
				}

				if (!mxClient.IS_QUIRKS)
				{
					var style = mxUtils.getCurrentStyle(this.contentWrapper);
		
					if (style.overflow == 'auto' || this.resize != null)
					{
						this.contentWrapper.style.height = (this.div.offsetHeight -
							this.title.offsetHeight - this.contentHeightCorrection) + 'px';
					}
				}

				this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt));
			}
			else
			{
				maximized = false;
				
				this.maximize.setAttribute('src', this.maximizeImage);
				this.maximize.setAttribute('title', 'Maximize');
				this.contentWrapper.style.display = '';
				this.minimize.style.display = minDisplay;

				// Restores window state
				this.div.style.left = x+'px';
				this.div.style.top = y+'px';
				
				if (!mxClient.IS_QUIRKS)
				{
					this.div.style.height = height;
					this.div.style.width = width;

					var style = mxUtils.getCurrentStyle(this.contentWrapper);
		
					if (style.overflow == 'auto' || this.resize != null)
					{
						this.contentWrapper.style.height = (this.div.offsetHeight -
							this.title.offsetHeight - this.contentHeightCorrection) + 'px';
					}
				}
				
				this.table.style.height = height;
				this.table.style.width = width;

				if (this.resize != null)
				{
					this.resize.style.visibility = '';
				}
				
				this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
			}
			
			mxEvent.consume(evt);
		}
	});
	
	mxEvent.addGestureListeners(this.maximize, funct);
	mxEvent.addListener(this.title, 'dblclick', funct);
};
	
/**
 * Function: installMoveHandler
 * 
 * Installs the event listeners required for moving the window.
 */
mxWindow.prototype.installMoveHandler = function()
{
	this.title.style.cursor = 'move';
	
	mxEvent.addGestureListeners(this.title,
		mxUtils.bind(this, function(evt)
		{
			var startX = mxEvent.getClientX(evt);
			var startY = mxEvent.getClientY(evt);
			var x = this.getX();
			var y = this.getY();
						
			// Adds a temporary pair of listeners to intercept
			// the gesture event in the document
			var dragHandler = mxUtils.bind(this, function(evt)
			{
				var dx = mxEvent.getClientX(evt) - startX;
				var dy = mxEvent.getClientY(evt) - startY;
				this.setLocation(x + dx, y + dy);
				this.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt));
				mxEvent.consume(evt);
			});
			
			var dropHandler = mxUtils.bind(this, function(evt)
			{
				mxEvent.removeGestureListeners(document, null, dragHandler, dropHandler);
				this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt));
				mxEvent.consume(evt);
			});
			
			mxEvent.addGestureListeners(document, null, dragHandler, dropHandler);
			this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt));
			mxEvent.consume(evt);
		}));
	
	// Disables built-in pan and zoom in IE10 and later
	if (mxClient.IS_POINTER)
	{
		this.title.style.touchAction = 'none';
	}
};

/**
 * Function: setLocation
 * 
 * Sets the upper, left corner of the window.
 */
 mxWindow.prototype.setLocation = function(x, y)
 {
	this.div.style.left = x + 'px';
	this.div.style.top = y + 'px';
 };

/**
 * Function: getX
 *
 * Returns the current position on the x-axis.
 */
mxWindow.prototype.getX = function()
{
	return parseInt(this.div.style.left);
};

/**
 * Function: getY
 *
 * Returns the current position on the y-axis.
 */
mxWindow.prototype.getY = function()
{
	return parseInt(this.div.style.top);
};

/**
 * Function: installCloseHandler
 *
 * Adds the  as a new image node in  and installs the
 *  event.
 */
mxWindow.prototype.installCloseHandler = function()
{
	this.closeImg = document.createElement('img');
	
	this.closeImg.setAttribute('src', this.closeImage);
	this.closeImg.setAttribute('title', 'Close');
	this.closeImg.style.marginLeft = '2px';
	this.closeImg.style.cursor = 'pointer';
	this.closeImg.style.display = 'none';
	
	this.buttons.appendChild(this.closeImg);

	mxEvent.addGestureListeners(this.closeImg,
		mxUtils.bind(this, function(evt)
		{
			this.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt));
			
			if (this.destroyOnClose)
			{
				this.destroy();
			}
			else
			{
				this.setVisible(false);
			}
			
			mxEvent.consume(evt);
		}));
};

/**
 * Function: setImage
 * 
 * Sets the image associated with the window.
 * 
 * Parameters:
 * 
 * image - URL of the image to be used.
 */
mxWindow.prototype.setImage = function(image)
{
	this.image = document.createElement('img');
	this.image.setAttribute('src', image);
	this.image.setAttribute('align', 'left');
	this.image.style.marginRight = '4px';
	this.image.style.marginLeft = '0px';
	this.image.style.marginTop = '-2px';
	
	this.title.insertBefore(this.image, this.title.firstChild);
};

/**
 * Function: setClosable
 * 
 * Sets the image associated with the window.
 * 
 * Parameters:
 * 
 * closable - Boolean specifying if the window should be closable.
 */
mxWindow.prototype.setClosable = function(closable)
{
	this.closeImg.style.display = (closable) ? '' : 'none';
};

/**
 * Function: isVisible
 * 
 * Returns true if the window is visible.
 */
mxWindow.prototype.isVisible = function()
{
	if (this.div != null)
	{
		return this.div.style.display != 'none';
	}
	
	return false;
};

/**
 * Function: setVisible
 *
 * Shows or hides the window depending on the given flag.
 * 
 * Parameters:
 * 
 * visible - Boolean indicating if the window should be made visible.
 */
mxWindow.prototype.setVisible = function(visible)
{
	if (this.div != null && this.isVisible() != visible)
	{
		if (visible)
		{
			this.show();
		}
		else
		{
			this.hide();
		}
	}
};

/**
 * Function: show
 *
 * Shows the window.
 */
mxWindow.prototype.show = function()
{
	this.div.style.display = '';
	this.activate();
	
	var style = mxUtils.getCurrentStyle(this.contentWrapper);
	
	if (!mxClient.IS_QUIRKS && (style.overflow == 'auto' || this.resize != null) &&
		this.contentWrapper.style.display != 'none')
	{
		this.contentWrapper.style.height = (this.div.offsetHeight -
				this.title.offsetHeight - this.contentHeightCorrection) + 'px';
	}
	
	this.fireEvent(new mxEventObject(mxEvent.SHOW));
};

/**
 * Function: hide
 *
 * Hides the window.
 */
mxWindow.prototype.hide = function()
{
	this.div.style.display = 'none';
	this.fireEvent(new mxEventObject(mxEvent.HIDE));
};

/**
 * Function: destroy
 *
 * Destroys the window and removes all associated resources. Fires a
 *  event prior to destroying the window.
 */
mxWindow.prototype.destroy = function()
{
	this.fireEvent(new mxEventObject(mxEvent.DESTROY));
	
	if (this.div != null)
	{
		mxEvent.release(this.div);
		this.div.parentNode.removeChild(this.div);
		this.div = null;
	}
	
	this.title = null;
	this.content = null;
	this.contentWrapper = null;
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxForm
 * 
 * A simple class for creating HTML forms.
 * 
 * Constructor: mxForm
 * 
 * Creates a HTML table using the specified classname.
 */
function mxForm(className)
{
	this.table = document.createElement('table');
	this.table.className = className;
	this.body = document.createElement('tbody');
	
	this.table.appendChild(this.body);
};

/**
 * Variable: table
 * 
 * Holds the DOM node that represents the table.
 */
mxForm.prototype.table = null;

/**
 * Variable: body
 * 
 * Holds the DOM node that represents the tbody (table body). New rows
 * can be added to this object using DOM API.
 */
mxForm.prototype.body = false;

/**
 * Function: getTable
 * 
 * Returns the table that contains this form.
 */
mxForm.prototype.getTable = function()
{
	return this.table;
};

/**
 * Function: addButtons
 * 
 * Helper method to add an OK and Cancel button using the respective
 * functions.
 */
mxForm.prototype.addButtons = function(okFunct, cancelFunct)
{
	var tr = document.createElement('tr');
	var td = document.createElement('td');
	tr.appendChild(td);
	td = document.createElement('td');

	// Adds the ok button
	var button = document.createElement('button');
	mxUtils.write(button, mxResources.get('ok') || 'OK');
	td.appendChild(button);

	mxEvent.addListener(button, 'click', function()
	{
		okFunct();
	});
	
	// Adds the cancel button
	button = document.createElement('button');
	mxUtils.write(button, mxResources.get('cancel') || 'Cancel');
	td.appendChild(button);
	
	mxEvent.addListener(button, 'click', function()
	{
		cancelFunct();
	});
	
	tr.appendChild(td);
	this.body.appendChild(tr);
};

/**
 * Function: addText
 * 
 * Adds an input for the given name, type and value and returns it.
 */
mxForm.prototype.addText = function(name, value, type)
{
	var input = document.createElement('input');
	
	input.setAttribute('type', type || 'text');
	input.value = value;
	
	return this.addField(name, input);
};

/**
 * Function: addCheckbox
 * 
 * Adds a checkbox for the given name and value and returns the textfield.
 */
mxForm.prototype.addCheckbox = function(name, value)
{
	var input = document.createElement('input');
	
	input.setAttribute('type', 'checkbox');
	this.addField(name, input);

	// IE can only change the checked value if the input is inside the DOM
	if (value)
	{
		input.checked = true;
	}

	return input;
};

/**
 * Function: addTextarea
 * 
 * Adds a textarea for the given name and value and returns the textarea.
 */
mxForm.prototype.addTextarea = function(name, value, rows)
{
	var input = document.createElement('textarea');
	
	if (mxClient.IS_NS)
	{
		rows--;
	}
	
	input.setAttribute('rows', rows || 2);
	input.value = value;
	
	return this.addField(name, input);
};

/**
 * Function: addCombo
 * 
 * Adds a combo for the given name and returns the combo.
 */
mxForm.prototype.addCombo = function(name, isMultiSelect, size)
{
	var select = document.createElement('select');
	
	if (size != null)
	{
		select.setAttribute('size', size);
	}
	
	if (isMultiSelect)
	{
		select.setAttribute('multiple', 'true');
	}
	
	return this.addField(name, select);
};

/**
 * Function: addOption
 * 
 * Adds an option for the given label to the specified combo.
 */
mxForm.prototype.addOption = function(combo, label, value, isSelected)
{
	var option = document.createElement('option');
	
	mxUtils.writeln(option, label);
	option.setAttribute('value', value);
	
	if (isSelected)
	{
		option.setAttribute('selected', isSelected);
	}
	
	combo.appendChild(option);
};

/**
 * Function: addField
 * 
 * Adds a new row with the name and the input field in two columns and
 * returns the given input.
 */
mxForm.prototype.addField = function(name, input)
{
	var tr = document.createElement('tr');
	var td = document.createElement('td');
	mxUtils.write(td, name);
	tr.appendChild(td);
	
	td = document.createElement('td');
	td.appendChild(input);
	tr.appendChild(td);
	this.body.appendChild(tr);
	
	return input;
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxImage
 *
 * Encapsulates the URL, width and height of an image.
 * 
 * Constructor: mxImage
 * 
 * Constructs a new image.
 */
function mxImage(src, width, height)
{
	this.src = src;
	this.width = width;
	this.height = height;
};

/**
 * Variable: src
 *
 * String that specifies the URL of the image.
 */
mxImage.prototype.src = null;

/**
 * Variable: width
 *
 * Integer that specifies the width of the image.
 */
mxImage.prototype.width = null;

/**
 * Variable: height
 *
 * Integer that specifies the height of the image.
 */
mxImage.prototype.height = null;
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDivResizer
 * 
 * Maintains the size of a div element in Internet Explorer. This is a
 * workaround for the right and bottom style being ignored in IE.
 * 
 * If you need a div to cover the scrollwidth and -height of a document,
 * then you can use this class as follows:
 * 
 * (code)
 * var resizer = new mxDivResizer(background);
 * resizer.getDocumentHeight = function()
 * {
 *   return document.body.scrollHeight;
 * }
 * resizer.getDocumentWidth = function()
 * {
 *   return document.body.scrollWidth;
 * }
 * resizer.resize();
 * (end)
 * 
 * Constructor: mxDivResizer
 * 
 * Constructs an object that maintains the size of a div
 * element when the window is being resized. This is only
 * required for Internet Explorer as it ignores the respective
 * stylesheet information for DIV elements.
 * 
 * Parameters:
 * 
 * div - Reference to the DOM node whose size should be maintained.
 * container - Optional Container that contains the div. Default is the
 * window.
 */
function mxDivResizer(div, container)
{
	if (div.nodeName.toLowerCase() == 'div')
	{
		if (container == null)
		{
			container = window;
		}

		this.div = div;
		var style = mxUtils.getCurrentStyle(div);
		
		if (style != null)
		{
			this.resizeWidth = style.width == 'auto';
			this.resizeHeight = style.height == 'auto';
		}
		
		mxEvent.addListener(container, 'resize',
			mxUtils.bind(this, function(evt)
			{
				if (!this.handlingResize)
				{
					this.handlingResize = true;
					this.resize();
					this.handlingResize = false;
				}
			})
		);
		
		this.resize();
	}
};

/**
 * Function: resizeWidth
 * 
 * Boolean specifying if the width should be updated.
 */
mxDivResizer.prototype.resizeWidth = true;

/**
 * Function: resizeHeight
 * 
 * Boolean specifying if the height should be updated.
 */
mxDivResizer.prototype.resizeHeight = true;

/**
 * Function: handlingResize
 * 
 * Boolean specifying if the width should be updated.
 */
mxDivResizer.prototype.handlingResize = false;

/**
 * Function: resize
 * 
 * Updates the style of the DIV after the window has been resized.
 */
mxDivResizer.prototype.resize = function()
{
	var w = this.getDocumentWidth();
	var h = this.getDocumentHeight();

	var l = parseInt(this.div.style.left);
	var r = parseInt(this.div.style.right);
	var t = parseInt(this.div.style.top);
	var b = parseInt(this.div.style.bottom);
	
	if (this.resizeWidth &&
		!isNaN(l) &&
		!isNaN(r) &&
		l >= 0 &&
		r >= 0 &&
		w - r - l > 0)
	{
		this.div.style.width = (w - r - l)+'px';
	}
	
	if (this.resizeHeight &&
		!isNaN(t) &&
		!isNaN(b) &&
		t >= 0 &&
		b >= 0 &&
		h - t - b > 0)
	{
		this.div.style.height = (h - t - b)+'px';
	}
};

/**
 * Function: getDocumentWidth
 * 
 * Hook for subclassers to return the width of the document (without
 * scrollbars).
 */
mxDivResizer.prototype.getDocumentWidth = function()
{
	return document.body.clientWidth;
};

/**
 * Function: getDocumentHeight
 * 
 * Hook for subclassers to return the height of the document (without
 * scrollbars).
 */
mxDivResizer.prototype.getDocumentHeight = function()
{
	return document.body.clientHeight;
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxDragSource
 * 
 * Wrapper to create a drag source from a DOM element so that the element can
 * be dragged over a graph and dropped into the graph as a new cell.
 * 
 * Problem is that in the dropHandler the current preview location is not
 * available, so the preview and the dropHandler must match.
 * 
 * Constructor: mxDragSource
 * 
 * Constructs a new drag source for the given element.
 */
function mxDragSource(element, dropHandler)
{
	this.element = element;
	this.dropHandler = dropHandler;
	
	// Handles a drag gesture on the element
	mxEvent.addGestureListeners(element, mxUtils.bind(this, function(evt)
	{
		this.mouseDown(evt);
	}));
	
	// Prevents native drag and drop
	mxEvent.addListener(element, 'dragstart', function(evt)
	{
		mxEvent.consume(evt);
	});
	
	this.eventConsumer = function(sender, evt)
	{
		var evtName = evt.getProperty('eventName');
		var me = evt.getProperty('event');
		
		if (evtName != mxEvent.MOUSE_DOWN)
		{
			me.consume();
		}
	};
};

/**
 * Variable: element
 *
 * Reference to the DOM node which was made draggable.
 */
mxDragSource.prototype.element = null;

/**
 * Variable: dropHandler
 *
 * Holds the DOM node that is used to represent the drag preview. If this is
 * null then the source element will be cloned and used for the drag preview.
 */
mxDragSource.prototype.dropHandler = null;

/**
 * Variable: dragOffset
 *
 *  that specifies the offset of the . Default is null.
 */
mxDragSource.prototype.dragOffset = null;

/**
 * Variable: dragElement
 *
 * Holds the DOM node that is used to represent the drag preview. If this is
 * null then the source element will be cloned and used for the drag preview.
 */
mxDragSource.prototype.dragElement = null;

/**
 * Variable: previewElement
 *
 * Optional  that specifies the unscaled size of the preview.
 */
mxDragSource.prototype.previewElement = null;

/**
 * Variable: enabled
 *
 * Specifies if this drag source is enabled. Default is true.
 */
mxDragSource.prototype.enabled = true;

/**
 * Variable: currentGraph
 *
 * Reference to the  that is the current drop target.
 */
mxDragSource.prototype.currentGraph = null;

/**
 * Variable: currentDropTarget
 *
 * Holds the current drop target under the mouse.
 */
mxDragSource.prototype.currentDropTarget = null;

/**
 * Variable: currentPoint
 *
 * Holds the current drop location.
 */
mxDragSource.prototype.currentPoint = null;

/**
 * Variable: currentGuide
 *
 * Holds an  for the  if  is not null.
 */
mxDragSource.prototype.currentGuide = null;

/**
 * Variable: currentGuide
 *
 * Holds an  for the  if  is not null.
 */
mxDragSource.prototype.currentHighlight = null;

/**
 * Variable: autoscroll
 *
 * Specifies if the graph should scroll automatically. Default is true.
 */
mxDragSource.prototype.autoscroll = true;

/**
 * Variable: guidesEnabled
 *
 * Specifies if  should be enabled. Default is true.
 */
mxDragSource.prototype.guidesEnabled = true;

/**
 * Variable: gridEnabled
 *
 * Specifies if the grid should be allowed. Default is true.
 */
mxDragSource.prototype.gridEnabled = true;

/**
 * Variable: highlightDropTargets
 *
 * Specifies if drop targets should be highlighted. Default is true.
 */
mxDragSource.prototype.highlightDropTargets = true;

/**
 * Variable: dragElementZIndex
 * 
 * ZIndex for the drag element. Default is 100.
 */
mxDragSource.prototype.dragElementZIndex = 100;

/**
 * Variable: dragElementOpacity
 * 
 * Opacity of the drag element in %. Default is 70.
 */
mxDragSource.prototype.dragElementOpacity = 70;

/**
 * Variable: checkEventSource
 * 
 * Whether the event source should be checked in . Default
 * is true.
 */
mxDragSource.prototype.checkEventSource = true;

/**
 * Function: isEnabled
 * 
 * Returns .
 */
mxDragSource.prototype.isEnabled = function()
{
	return this.enabled;
};

/**
 * Function: setEnabled
 * 
 * Sets .
 */
mxDragSource.prototype.setEnabled = function(value)
{
	this.enabled = value;
};

/**
 * Function: isGuidesEnabled
 * 
 * Returns .
 */
mxDragSource.prototype.isGuidesEnabled = function()
{
	return this.guidesEnabled;
};

/**
 * Function: setGuidesEnabled
 * 
 * Sets .
 */
mxDragSource.prototype.setGuidesEnabled = function(value)
{
	this.guidesEnabled = value;
};

/**
 * Function: isGridEnabled
 * 
 * Returns .
 */
mxDragSource.prototype.isGridEnabled = function()
{
	return this.gridEnabled;
};

/**
 * Function: setGridEnabled
 * 
 * Sets .
 */
mxDragSource.prototype.setGridEnabled = function(value)
{
	this.gridEnabled = value;
};

/**
 * Function: getGraphForEvent
 * 
 * Returns the graph for the given mouse event. This implementation returns
 * null.
 */
mxDragSource.prototype.getGraphForEvent = function(evt)
{
	return null;
};

/**
 * Function: getDropTarget
 * 
 * Returns the drop target for the given graph and coordinates. This
 * implementation uses .
 */
mxDragSource.prototype.getDropTarget = function(graph, x, y, evt)
{
	return graph.getCellAt(x, y);
};

/**
 * Function: createDragElement
 * 
 * Creates and returns a clone of the  or the 
 * if the former is not defined.
 */
mxDragSource.prototype.createDragElement = function(evt)
{
	return this.element.cloneNode(true);
};

/**
 * Function: createPreviewElement
 * 
 * Creates and returns an element which can be used as a preview in the given
 * graph.
 */
mxDragSource.prototype.createPreviewElement = function(graph)
{
	return null;
};

/**
 * Function: isActive
 * 
 * Returns true if this drag source is active.
 */
mxDragSource.prototype.isActive = function()
{
	return this.mouseMoveHandler != null;
};

/**
 * Function: reset
 * 
 * Stops and removes everything and restores the state of the object.
 */
mxDragSource.prototype.reset = function()
{
	if (this.currentGraph != null)
	{
		this.dragExit(this.currentGraph);
		this.currentGraph = null;
	}
	
	this.removeDragElement();
	this.removeListeners();
	this.stopDrag();
};

/**
 * Function: mouseDown
 * 
 * Returns the drop target for the given graph and coordinates. This
 * implementation uses .
 * 
 * To ignore popup menu events for a drag source, this function can be
 * overridden as follows.
 * 
 * (code)
 * var mouseDown = dragSource.mouseDown;
 * 
 * dragSource.mouseDown = function(evt)
 * {
 *   if (!mxEvent.isPopupTrigger(evt))
 *   {
 *     mouseDown.apply(this, arguments);
 *   }
 * };
 * (end)
 */
mxDragSource.prototype.mouseDown = function(evt)
{
	if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null)
	{
		this.startDrag(evt);
		this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove);
		this.mouseUpHandler = mxUtils.bind(this, this.mouseUp);		
		mxEvent.addGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
		
		if (mxClient.IS_TOUCH && !mxEvent.isMouseEvent(evt))
		{
			this.eventSource = mxEvent.getSource(evt);
			mxEvent.addGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
		}
	}
};

/**
 * Function: startDrag
 * 
 * Creates the  using .
 */
mxDragSource.prototype.startDrag = function(evt)
{
	this.dragElement = this.createDragElement(evt);
	this.dragElement.style.position = 'absolute';
	this.dragElement.style.zIndex = this.dragElementZIndex;
	mxUtils.setOpacity(this.dragElement, this.dragElementOpacity);

	if (this.checkEventSource && mxClient.IS_SVG)
	{
		this.dragElement.style.pointerEvents = 'none';
	}
};

/**
 * Function: stopDrag
 * 
 * Invokes .
 */
mxDragSource.prototype.stopDrag = function()
{
	// LATER: This used to have a mouse event. If that is still needed we need to add another
	// final call to the DnD protocol to add a cleanup step in the case of escape press, which
	// is not associated with a mouse event and which currently calles this method.
	this.removeDragElement();
};

/**
 * Function: removeDragElement
 * 
 * Removes and destroys the .
 */
mxDragSource.prototype.removeDragElement = function()
{
	if (this.dragElement != null)
	{
		if (this.dragElement.parentNode != null)
		{
			this.dragElement.parentNode.removeChild(this.dragElement);
		}
		
		this.dragElement = null;
	}
};

/**
 * Function: getElementForEvent
 * 
 * Returns the topmost element under the given event.
 */
mxDragSource.prototype.getElementForEvent = function(evt)
{
	return ((mxEvent.isTouchEvent(evt) || mxEvent.isPenEvent(evt)) ?
			document.elementFromPoint(mxEvent.getClientX(evt), mxEvent.getClientY(evt)) :
				mxEvent.getSource(evt));
};

/**
 * Function: graphContainsEvent
 * 
 * Returns true if the given graph contains the given event.
 */
mxDragSource.prototype.graphContainsEvent = function(graph, evt)
{
	var x = mxEvent.getClientX(evt);
	var y = mxEvent.getClientY(evt);
	var offset = mxUtils.getOffset(graph.container);
	var origin = mxUtils.getScrollOrigin();
	var elt = this.getElementForEvent(evt);
	
	if (this.checkEventSource)
	{
		while (elt != null && elt != graph.container)
		{
			elt = elt.parentNode;
		}
	}

	// Checks if event is inside the bounds of the graph container
	return elt != null && x >= offset.x - origin.x && y >= offset.y - origin.y &&
		x <= offset.x - origin.x + graph.container.offsetWidth &&
		y <= offset.y - origin.y + graph.container.offsetHeight;
};

/**
 * Function: mouseMove
 * 
 * Gets the graph for the given event using , updates the
 * , calling  and  on the new and old graph,
 * respectively, and invokes  if  is not null.
 */
mxDragSource.prototype.mouseMove = function(evt)
{
	var graph = this.getGraphForEvent(evt);
	
	// Checks if event is inside the bounds of the graph container
	if (graph != null && !this.graphContainsEvent(graph, evt))
	{
		graph = null;
	}

	if (graph != this.currentGraph)
	{
		if (this.currentGraph != null)
		{
			this.dragExit(this.currentGraph, evt);
		}
		
		this.currentGraph = graph;
		
		if (this.currentGraph != null)
		{
			this.dragEnter(this.currentGraph, evt);
		}
	}
	
	if (this.currentGraph != null)
	{
		this.dragOver(this.currentGraph, evt);
	}

	if (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible'))
	{
		var x = mxEvent.getClientX(evt);
		var y = mxEvent.getClientY(evt);
		
		if (this.dragElement.parentNode == null)
		{
			document.body.appendChild(this.dragElement);
		}

		this.dragElement.style.visibility = 'visible';
		
		if (this.dragOffset != null)
		{
			x += this.dragOffset.x;
			y += this.dragOffset.y;
		}
		
		var offset = mxUtils.getDocumentScrollOrigin(document);
		
		this.dragElement.style.left = (x + offset.x) + 'px';
		this.dragElement.style.top = (y + offset.y) + 'px';
	}
	else if (this.dragElement != null)
	{
		this.dragElement.style.visibility = 'hidden';
	}
	
	mxEvent.consume(evt);
};

/**
 * Function: mouseUp
 * 
 * Processes the mouse up event and invokes ,  and 
 * as required.
 */
mxDragSource.prototype.mouseUp = function(evt)
{
	if (this.currentGraph != null)
	{
		if (this.currentPoint != null && (this.previewElement == null ||
			this.previewElement.style.visibility != 'hidden'))
		{
			var scale = this.currentGraph.view.scale;
			var tr = this.currentGraph.view.translate;
			var x = this.currentPoint.x / scale - tr.x;
			var y = this.currentPoint.y / scale - tr.y;
			
			this.drop(this.currentGraph, evt, this.currentDropTarget, x, y);
		}
		
		this.dragExit(this.currentGraph);
		this.currentGraph = null;
	}

	this.stopDrag();
	this.removeListeners();
	
	mxEvent.consume(evt);
};

/**
 * Function: removeListeners
 * 
 * Actives the given graph as a drop target.
 */
mxDragSource.prototype.removeListeners = function()
{
	if (this.eventSource != null)
	{
		mxEvent.removeGestureListeners(this.eventSource, null, this.mouseMoveHandler, this.mouseUpHandler);
		this.eventSource = null;
	}
	
	mxEvent.removeGestureListeners(document, null, this.mouseMoveHandler, this.mouseUpHandler);
	this.mouseMoveHandler = null;
	this.mouseUpHandler = null;
};

/**
 * Function: dragEnter
 * 
 * Actives the given graph as a drop target.
 */
mxDragSource.prototype.dragEnter = function(graph, evt)
{
	graph.isMouseDown = true;
	graph.isMouseTrigger = mxEvent.isMouseEvent(evt);
	this.previewElement = this.createPreviewElement(graph);
	
	if (this.previewElement != null && this.checkEventSource && mxClient.IS_SVG)
	{
		this.previewElement.style.pointerEvents = 'none';
	}
	
	// Guide is only needed if preview element is used
	if (this.isGuidesEnabled() && this.previewElement != null)
	{
		this.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates());
	}
	
	if (this.highlightDropTargets)
	{
		this.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR);
	}
	
	// Consumes all events in the current graph before they are fired
	graph.addListener(mxEvent.FIRE_MOUSE_EVENT, this.eventConsumer);
};

/**
 * Function: dragExit
 * 
 * Deactivates the given graph as a drop target.
 */
mxDragSource.prototype.dragExit = function(graph, evt)
{
	this.currentDropTarget = null;
	this.currentPoint = null;
	graph.isMouseDown = false;
	
	// Consumes all events in the current graph before they are fired
	graph.removeListener(this.eventConsumer);
	
	if (this.previewElement != null)
	{
		if (this.previewElement.parentNode != null)
		{
			this.previewElement.parentNode.removeChild(this.previewElement);
		}
		
		this.previewElement = null;
	}
	
	if (this.currentGuide != null)
	{
		this.currentGuide.destroy();
		this.currentGuide = null;
	}
	
	if (this.currentHighlight != null)
	{
		this.currentHighlight.destroy();
		this.currentHighlight = null;
	}
};

/**
 * Function: dragOver
 * 
 * Implements autoscroll, updates the , highlights any drop
 * targets and updates the preview.
 */
mxDragSource.prototype.dragOver = function(graph, evt)
{
	var offset = mxUtils.getOffset(graph.container);
	var origin = mxUtils.getScrollOrigin(graph.container);
	var x = mxEvent.getClientX(evt) - offset.x + origin.x - graph.panDx;
	var y = mxEvent.getClientY(evt) - offset.y + origin.y - graph.panDy;

	if (graph.autoScroll && (this.autoscroll == null || this.autoscroll))
	{
		graph.scrollPointToVisible(x, y, graph.autoExtend);
	}

	// Highlights the drop target under the mouse
	if (this.currentHighlight != null && graph.isDropEnabled())
	{
		this.currentDropTarget = this.getDropTarget(graph, x, y, evt);
		var state = graph.getView().getState(this.currentDropTarget);
		this.currentHighlight.highlight(state);
	}

	// Updates the location of the preview
	if (this.previewElement != null)
	{
		if (this.previewElement.parentNode == null)
		{
			graph.container.appendChild(this.previewElement);
			
			this.previewElement.style.zIndex = '3';
			this.previewElement.style.position = 'absolute';
		}
		
		var gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt);
		var hideGuide = true;

		// Grid and guides
		if (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt))
		{
			// LATER: HTML preview appears smaller than SVG preview
			var w = parseInt(this.previewElement.style.width);
			var h = parseInt(this.previewElement.style.height);
			var bounds = new mxRectangle(0, 0, w, h);
			var delta = new mxPoint(x, y);
			delta = this.currentGuide.move(bounds, delta, gridEnabled, true);
			hideGuide = false;
			x = delta.x;
			y = delta.y;
		}
		else if (gridEnabled)
		{
			var scale = graph.view.scale;
			var tr = graph.view.translate;
			var off = graph.gridSize / 2;
			x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;
			y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;
		}
		
		if (this.currentGuide != null && hideGuide)
		{
			this.currentGuide.hide();
		}
		
		if (this.previewOffset != null)
		{
			x += this.previewOffset.x;
			y += this.previewOffset.y;
		}

		this.previewElement.style.left = Math.round(x) + 'px';
		this.previewElement.style.top = Math.round(y) + 'px';
		this.previewElement.style.visibility = 'visible';
	}
	
	this.currentPoint = new mxPoint(x, y);
};

/**
 * Function: drop
 * 
 * Returns the drop target for the given graph and coordinates. This
 * implementation uses .
 */
mxDragSource.prototype.drop = function(graph, evt, dropTarget, x, y)
{
	this.dropHandler.apply(this, arguments);
	
	// Had to move this to after the insert because it will
	// affect the scrollbars of the window in IE to try and
	// make the complete container visible.
	// LATER: Should be made optional.
	if (graph.container.style.visibility != 'hidden')
	{
		graph.container.focus();
	}
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxToolbar
 * 
 * Creates a toolbar inside a given DOM node. The toolbar may contain icons,
 * buttons and combo boxes.
 * 
 * Event: mxEvent.SELECT
 * 
 * Fires when an item was selected in the toolbar. The function
 * property contains the function that was selected in .
 * 
 * Constructor: mxToolbar
 * 
 * Constructs a toolbar in the specified container.
 *
 * Parameters:
 *
 * container - DOM node that contains the toolbar.
 */
function mxToolbar(container)
{
	this.container = container;
};

/**
 * Extends mxEventSource.
 */
mxToolbar.prototype = new mxEventSource();
mxToolbar.prototype.constructor = mxToolbar;

/**
 * Variable: container
 * 
 * Reference to the DOM nodes that contains the toolbar.
 */
mxToolbar.prototype.container = null;

/**
 * Variable: enabled
 * 
 * Specifies if events are handled. Default is true.
 */
mxToolbar.prototype.enabled = true;

/**
 * Variable: noReset
 * 
 * Specifies if  requires a forced flag of true for resetting
 * the current mode in the toolbar. Default is false. This is set to true
 * if the toolbar item is double clicked to avoid a reset after a single
 * use of the item.
 */
mxToolbar.prototype.noReset = false;

/**
 * Variable: updateDefaultMode
 * 
 * Boolean indicating if the default mode should be the last selected
 * switch mode or the first inserted switch mode. Default is true, that
 * is the last selected switch mode is the default mode. The default mode
 * is the mode to be selected after a reset of the toolbar. If this is
 * false, then the default mode is the first inserted mode item regardless
 * of what was last selected. Otherwise, the selected item after a reset is
 * the previously selected item.
 */
mxToolbar.prototype.updateDefaultMode = true;

/**
 * Function: addItem
 * 
 * Adds the given function as an image with the specified title and icon
 * and returns the new image node.
 * 
 * Parameters:
 * 
 * title - Optional string that is used as the tooltip.
 * icon - Optional URL of the image to be used. If no URL is given, then a
 * button is created.
 * funct - Function to execute on a mouse click.
 * pressedIcon - Optional URL of the pressed image. Default is a gray
 * background.
 * style - Optional style classname. Default is mxToolbarItem.
 * factoryMethod - Optional factory method for popup menu, eg.
 * function(menu, evt, cell) { menu.addItem('Hello, World!'); }
 */
mxToolbar.prototype.addItem = function(title, icon, funct, pressedIcon, style, factoryMethod)
{
	var img = document.createElement((icon != null) ? 'img' : 'button');
	var initialClassName = style || ((factoryMethod != null) ?
			'mxToolbarMode' : 'mxToolbarItem');
	img.className = initialClassName;
	img.setAttribute('src', icon);
	
	if (title != null)
	{
		if (icon != null)
		{
			img.setAttribute('title', title);
		}
		else
		{
			mxUtils.write(img, title);
		}
	}
	
	this.container.appendChild(img);

	// Invokes the function on a click on the toolbar item
	if (funct != null)
	{
		mxEvent.addListener(img, 'click', funct);
		
		if (mxClient.IS_TOUCH)
		{
			mxEvent.addListener(img, 'touchend', funct);
		}
	}

	var mouseHandler = mxUtils.bind(this, function(evt)
	{
		if (pressedIcon != null)
		{
			img.setAttribute('src', icon);
		}
		else
		{
			img.style.backgroundColor = '';
		}
	});

	// Highlights the toolbar item with a gray background
	// while it is being clicked with the mouse
	mxEvent.addGestureListeners(img, mxUtils.bind(this, function(evt)
	{
		if (pressedIcon != null)
		{
			img.setAttribute('src', pressedIcon);
		}
		else
		{
			img.style.backgroundColor = 'gray';
		}
		
		// Popup Menu
		if (factoryMethod != null)
		{
			if (this.menu == null)
			{
				this.menu = new mxPopupMenu();
				this.menu.init();
			}
			
			var last = this.currentImg;
			
			if (this.menu.isMenuShowing())
			{
				this.menu.hideMenu();
			}
			
			if (last != img)
			{
				// Redirects factory method to local factory method
				this.currentImg = img;
				this.menu.factoryMethod = factoryMethod;
				
				var point = new mxPoint(
					img.offsetLeft,
					img.offsetTop + img.offsetHeight);
				this.menu.popup(point.x, point.y, null, evt);

				// Sets and overrides to restore classname
				if (this.menu.isMenuShowing())
				{
					img.className = initialClassName + 'Selected';
					
					this.menu.hideMenu = function()
					{
						mxPopupMenu.prototype.hideMenu.apply(this);
						img.className = initialClassName;
						this.currentImg = null;
					};
				}
			}
		}
	}), null, mouseHandler);

	mxEvent.addListener(img, 'mouseout', mouseHandler);
	
	return img;
};

/**
 * Function: addCombo
 * 
 * Adds and returns a new SELECT element using the given style. The element
 * is placed inside a DIV with the mxToolbarComboContainer style classname.
 * 
 * Parameters:
 * 
 * style - Optional style classname. Default is mxToolbarCombo.
 */
mxToolbar.prototype.addCombo = function(style)
{
	var div = document.createElement('div');
	div.style.display = 'inline';
	div.className = 'mxToolbarComboContainer';
	
	var select = document.createElement('select');
	select.className = style || 'mxToolbarCombo';
	div.appendChild(select);
	
	this.container.appendChild(div);
	
	return select;
};

/**
 * Function: addCombo
 * 
 * Adds and returns a new SELECT element using the given title as the
 * default element. The selection is reset to this element after each
 * change.
 * 
 * Parameters:
 * 
 * title - String that specifies the title of the default element.
 * style - Optional style classname. Default is mxToolbarCombo.
 */
mxToolbar.prototype.addActionCombo = function(title, style)
{
	var select = document.createElement('select');
	select.className = style || 'mxToolbarCombo';
	this.addOption(select, title, null);
	
	mxEvent.addListener(select, 'change', function(evt)
	{
		var value = select.options[select.selectedIndex];
		select.selectedIndex = 0;
		
		if (value.funct != null)
		{
			value.funct(evt);
		}
	});
	
	this.container.appendChild(select);
	
	return select;
};

/**
 * Function: addOption
 * 
 * Adds and returns a new OPTION element inside the given SELECT element.
 * If the given value is a function then it is stored in the option's funct
 * field.
 * 
 * Parameters:
 * 
 * combo - SELECT element that will contain the new entry.
 * title - String that specifies the title of the option.
 * value - Specifies the value associated with this option.
 */
mxToolbar.prototype.addOption = function(combo, title, value)
{
	var option = document.createElement('option');
	mxUtils.writeln(option, title);
	
	if (typeof(value) == 'function')
	{
		option.funct = value;
	}
	else
	{
		option.setAttribute('value', value);
	}
	
	combo.appendChild(option);
	
	return option;
};

/**
 * Function: addSwitchMode
 * 
 * Adds a new selectable item to the toolbar. Only one switch mode item may
 * be selected at a time. The currently selected item is the default item
 * after a reset of the toolbar.
 */
mxToolbar.prototype.addSwitchMode = function(title, icon, funct, pressedIcon, style)
{
	var img = document.createElement('img');
	img.initialClassName = style || 'mxToolbarMode';
	img.className = img.initialClassName;
	img.setAttribute('src', icon);
	img.altIcon = pressedIcon;
	
	if (title != null)
	{
		img.setAttribute('title', title);
	}
	
	mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
	{
		var tmp = this.selectedMode.altIcon;
		
		if (tmp != null)
		{
			this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
			this.selectedMode.setAttribute('src', tmp);
		}
		else
		{
			this.selectedMode.className = this.selectedMode.initialClassName;
		}
		
		if (this.updateDefaultMode)
		{
			this.defaultMode = img;
		}
		
		this.selectedMode = img;
		
		var tmp = img.altIcon;
		
		if (tmp != null)
		{
			img.altIcon = img.getAttribute('src');
			img.setAttribute('src', tmp);
		}
		else
		{
			img.className = img.initialClassName+'Selected';
		}
		
		this.fireEvent(new mxEventObject(mxEvent.SELECT));
		funct();
	}));
	
	this.container.appendChild(img);
	
	if (this.defaultMode == null)
	{
		this.defaultMode = img;
		
		// Function should fire only once so
		// do not pass it with the select event
		this.selectMode(img);
		funct();
	}
	
	return img;
};

/**
 * Function: addMode
 * 
 * Adds a new item to the toolbar. The selection is typically reset after
 * the item has been consumed, for example by adding a new vertex to the
 * graph. The reset is not carried out if the item is double clicked.
 * 
 * The function argument uses the following signature: funct(evt, cell) where
 * evt is the native mouse event and cell is the cell under the mouse.
 */
mxToolbar.prototype.addMode = function(title, icon, funct, pressedIcon, style, toggle)
{
	toggle = (toggle != null) ? toggle : true;
	var img = document.createElement((icon != null) ? 'img' : 'button');
	
	img.initialClassName = style || 'mxToolbarMode';
	img.className = img.initialClassName;
	img.setAttribute('src', icon);
	img.altIcon = pressedIcon;

	if (title != null)
	{
		img.setAttribute('title', title);
	}
	
	if (this.enabled && toggle)
	{
		mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
		{
			this.selectMode(img, funct);
			this.noReset = false;
		}));
		
		mxEvent.addListener(img, 'dblclick', mxUtils.bind(this, function(evt)
		{
			this.selectMode(img, funct);
			this.noReset = true;
		}));
		
		if (this.defaultMode == null)
		{
			this.defaultMode = img;
			this.defaultFunction = funct;
			this.selectMode(img, funct);
		}
	}

	this.container.appendChild(img);					

	return img;
};

/**
 * Function: selectMode
 * 
 * Resets the state of the previously selected mode and displays the given
 * DOM node as selected. This function fires a select event with the given
 * function as a parameter.
 */
mxToolbar.prototype.selectMode = function(domNode, funct)
{
	if (this.selectedMode != domNode)
	{
		if (this.selectedMode != null)
		{
			var tmp = this.selectedMode.altIcon;
			
			if (tmp != null)
			{
				this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
				this.selectedMode.setAttribute('src', tmp);
			}
			else
			{
				this.selectedMode.className = this.selectedMode.initialClassName;
			}
		}
		
		this.selectedMode = domNode;
		var tmp = this.selectedMode.altIcon;
		
		if (tmp != null)
		{
			this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
			this.selectedMode.setAttribute('src', tmp);
		}
		else
		{
			this.selectedMode.className = this.selectedMode.initialClassName+'Selected';
		}
		
		this.fireEvent(new mxEventObject(mxEvent.SELECT, "function", funct));
	}
};

/**
 * Function: resetMode
 * 
 * Selects the default mode and resets the state of the previously selected
 * mode.
 */
mxToolbar.prototype.resetMode = function(forced)
{
	if ((forced || !this.noReset) && this.selectedMode != this.defaultMode)
	{
		// The last selected switch mode will be activated
		// so the function was already executed and is
		// no longer required here
		this.selectMode(this.defaultMode, this.defaultFunction);
	}
};

/**
 * Function: addSeparator
 * 
 * Adds the specifies image as a separator.
 * 
 * Parameters:
 * 
 * icon - URL of the separator icon.
 */
mxToolbar.prototype.addSeparator = function(icon)
{
	return this.addItem(null, icon, null);
};

/**
 * Function: addBreak
 * 
 * Adds a break to the container.
 */
mxToolbar.prototype.addBreak = function()
{
	mxUtils.br(this.container);
};

/**
 * Function: addLine
 * 
 * Adds a horizontal line to the container.
 */
mxToolbar.prototype.addLine = function()
{
	var hr = document.createElement('hr');
	
	hr.style.marginRight = '6px';
	hr.setAttribute('size', '1');
	
	this.container.appendChild(hr);
};

/**
 * Function: destroy
 * 
 * Removes the toolbar and all its associated resources.
 */
mxToolbar.prototype.destroy = function ()
{
	mxEvent.release(this.container);
	this.container = null;
	this.defaultMode = null;
	this.defaultFunction = null;
	this.selectedMode = null;
	
	if (this.menu != null)
	{
		this.menu.destroy();
	}
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxUndoableEdit
 * 
 * Implements a composite undoable edit. Here is an example for a custom change
 * which gets executed via the model:
 * 
 * (code)
 * function CustomChange(model, name)
 * {
 *   this.model = model;
 *   this.name = name;
 *   this.previous = name;
 * };
 * 
 * CustomChange.prototype.execute = function()
 * {
 *   var tmp = this.model.name;
 *   this.model.name = this.previous;
 *   this.previous = tmp;
 * };
 * 
 * var name = prompt('Enter name');
 * graph.model.execute(new CustomChange(graph.model, name));
 * (end)
 * 
 * Event: mxEvent.EXECUTED
 * 
 * Fires between START_EDIT and END_EDIT after an atomic change was executed.
 * The change property contains the change that was executed.
 * 
 * Event: mxEvent.START_EDIT
 * 
 * Fires before a set of changes will be executed in  or .
 * This event contains no properties.
 * 
 * Event: mxEvent.END_EDIT
 *
 * Fires after a set of changeswas executed in  or .
 * This event contains no properties.
 * 
 * Constructor: mxUndoableEdit
 * 
 * Constructs a new undoable edit for the given source.
 */
function mxUndoableEdit(source, significant)
{
	this.source = source;
	this.changes = [];
	this.significant = (significant != null) ? significant : true;
};

/**
 * Variable: source
 * 
 * Specifies the source of the edit.
 */
mxUndoableEdit.prototype.source = null;

/**
 * Variable: changes
 * 
 * Array that contains the changes that make up this edit. The changes are
 * expected to either have an undo and redo function, or an execute
 * function. Default is an empty array.
 */
mxUndoableEdit.prototype.changes = null;

/**
 * Variable: significant
 * 
 * Specifies if the undoable change is significant.
 * Default is true.
 */
mxUndoableEdit.prototype.significant = null;

/**
 * Variable: undone
 * 
 * Specifies if this edit has been undone. Default is false.
 */
mxUndoableEdit.prototype.undone = false;

/**
 * Variable: redone
 * 
 * Specifies if this edit has been redone. Default is false.
 */
mxUndoableEdit.prototype.redone = false;

/**
 * Function: isEmpty
 * 
 * Returns true if the this edit contains no changes.
 */
mxUndoableEdit.prototype.isEmpty = function()
{
	return this.changes.length == 0;
};

/**
 * Function: isSignificant
 * 
 * Returns .
 */
mxUndoableEdit.prototype.isSignificant = function()
{
	return this.significant;
};

/**
 * Function: add
 * 
 * Adds the specified change to this edit. The change is an object that is
 * expected to either have an undo and redo, or an execute function.
 */
mxUndoableEdit.prototype.add = function(change)
{
	this.changes.push(change);
};

/**
 * Function: notify
 * 
 * Hook to notify any listeners of the changes after an  or 
 * has been carried out. This implementation is empty.
 */
mxUndoableEdit.prototype.notify = function() { };

/**
 * Function: die
 * 
 * Hook to free resources after the edit has been removed from the command
 * history. This implementation is empty.
 */
mxUndoableEdit.prototype.die = function() { };

/**
 * Function: undo
 * 
 * Undoes all changes in this edit.
 */
mxUndoableEdit.prototype.undo = function()
{
	if (!this.undone)
	{
		this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
		var count = this.changes.length;
		
		for (var i = count - 1; i >= 0; i--)
		{
			var change = this.changes[i];
			
			if (change.execute != null)
			{
				change.execute();
			}
			else if (change.undo != null)
			{
				change.undo();
			}
			
			// New global executed event
			this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
		}
		
		this.undone = true;
		this.redone = false;
		this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
	}
	
	this.notify();
};

/**
 * Function: redo
 * 
 * Redoes all changes in this edit.
 */
mxUndoableEdit.prototype.redo = function()
{
	if (!this.redone)
	{
		this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));
		var count = this.changes.length;
		
		for (var i = 0; i < count; i++)
		{
			var change = this.changes[i];
			
			if (change.execute != null)
			{
				change.execute();
			}
			else if (change.redo != null)
			{
				change.redo();
			}
			
			// New global executed event
			this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED, 'change', change));
		}
		
		this.undone = false;
		this.redone = true;
		this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT));
	}
	
	this.notify();
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxUndoManager
 *
 * Implements a command history. When changing the graph model, an
 *  object is created at the start of the transaction (when
 * model.beginUpdate is called). All atomic changes are then added to this
 * object until the last model.endUpdate call, at which point the
 *  is dispatched in an event, and added to the history inside
 * . This is done by an event listener in
 * .
 * 
 * Each atomic change of the model is represented by an object (eg.
 * , ,  etc) which contains the
 * complete undo information. The  also listens to the
 *  and stores it's changes to the current root as insignificant
 * undoable changes, so that drilling (step into, step up) is undone.
 * 
 * This means when you execute an atomic change on the model, then change the
 * current root on the view and click undo, the change of the root will be
 * undone together with the change of the model so that the display represents
 * the state at which the model was changed. However, these changes are not
 * transmitted for sharing as they do not represent a state change.
 *
 * Example:
 * 
 * When adding an undo manager to a graph, make sure to add it
 * to the model and the view as well to maintain a consistent
 * display across multiple undo/redo steps.
 *
 * (code)
 * var undoManager = new mxUndoManager();
 * var listener = function(sender, evt)
 * {
 *   undoManager.undoableEditHappened(evt.getProperty('edit'));
 * };
 * graph.getModel().addListener(mxEvent.UNDO, listener);
 * graph.getView().addListener(mxEvent.UNDO, listener);
 * (end)
 * 
 * The code creates a function that informs the undoManager
 * of an undoable edit and binds it to the undo event of
 *  and  using
 * .
 * 
 * Event: mxEvent.CLEAR
 * 
 * Fires after  was invoked. This event has no properties.
 * 
 * Event: mxEvent.UNDO
 * 
 * Fires afer a significant edit was undone in . The edit
 * property contains the  that was undone.
 * 
 * Event: mxEvent.REDO
 * 
 * Fires afer a significant edit was redone in . The edit
 * property contains the  that was redone.
 * 
 * Event: mxEvent.ADD
 * 
 * Fires after an undoable edit was added to the history. The edit
 * property contains the  that was added.
 * 
 * Constructor: mxUndoManager
 *
 * Constructs a new undo manager with the given history size. If no history
 * size is given, then a default size of 100 steps is used.
 */
function mxUndoManager(size)
{
	this.size = (size != null) ? size : 100;
	this.clear();
};

/**
 * Extends mxEventSource.
 */
mxUndoManager.prototype = new mxEventSource();
mxUndoManager.prototype.constructor = mxUndoManager;

/**
 * Variable: size
 * 
 * Maximum command history size. 0 means unlimited history. Default is
 * 100.
 */
mxUndoManager.prototype.size = null;

/**
 * Variable: history
 * 
 * Array that contains the steps of the command history.
 */
mxUndoManager.prototype.history = null;

/**
 * Variable: indexOfNextAdd
 * 
 * Index of the element to be added next.
 */
mxUndoManager.prototype.indexOfNextAdd = 0;

/**
 * Function: isEmpty
 * 
 * Returns true if the history is empty.
 */
mxUndoManager.prototype.isEmpty = function()
{
	return this.history.length == 0;
};

/**
 * Function: clear
 * 
 * Clears the command history.
 */
mxUndoManager.prototype.clear = function()
{
	this.history = [];
	this.indexOfNextAdd = 0;
	this.fireEvent(new mxEventObject(mxEvent.CLEAR));
};

/**
 * Function: canUndo
 * 
 * Returns true if an undo is possible.
 */
mxUndoManager.prototype.canUndo = function()
{
	return this.indexOfNextAdd > 0;
};

/**
 * Function: undo
 * 
 * Undoes the last change.
 */
mxUndoManager.prototype.undo = function()
{
    while (this.indexOfNextAdd > 0)
    {
        var edit = this.history[--this.indexOfNextAdd];
        edit.undo();

		if (edit.isSignificant())
        {
        	this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
            break;
        }
    }
};

/**
 * Function: canRedo
 * 
 * Returns true if a redo is possible.
 */
mxUndoManager.prototype.canRedo = function()
{
	return this.indexOfNextAdd < this.history.length;
};

/**
 * Function: redo
 * 
 * Redoes the last change.
 */
mxUndoManager.prototype.redo = function()
{
    var n = this.history.length;
    
    while (this.indexOfNextAdd < n)
    {
        var edit =  this.history[this.indexOfNextAdd++];
        edit.redo();
        
        if (edit.isSignificant())
        {
        	this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));
            break;
        }
    }
};

/**
 * Function: undoableEditHappened
 * 
 * Method to be called to add new undoable edits to the .
 */
mxUndoManager.prototype.undoableEditHappened = function(undoableEdit)
{
	this.trim();
	
	if (this.size > 0 &&
		this.size == this.history.length)
	{
		this.history.shift();
	}
	
	this.history.push(undoableEdit);
	this.indexOfNextAdd = this.history.length;
	this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));
};

/**
 * Function: trim
 * 
 * Removes all pending steps after  from the history,
 * invoking die on each edit. This is called from .
 */
mxUndoManager.prototype.trim = function()
{
	if (this.history.length > this.indexOfNextAdd)
	{
		var edits = this.history.splice(this.indexOfNextAdd,
			this.history.length - this.indexOfNextAdd);
			
		for (var i = 0; i < edits.length; i++)
		{
			edits[i].die();
		}
	}
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 *
 * Class: mxUrlConverter
 * 
 * Converts relative and absolute URLs to absolute URLs with protocol and domain.
 */
var mxUrlConverter = function()
{
	// Empty constructor
};

/**
 * Variable: enabled
 * 
 * Specifies if the converter is enabled. Default is true.
 */
mxUrlConverter.prototype.enabled = true;

/**
 * Variable: baseUrl
 * 
 * Specifies the base URL to be used as a prefix for relative URLs.
 */
mxUrlConverter.prototype.baseUrl = null;

/**
 * Variable: baseDomain
 * 
 * Specifies the base domain to be used as a prefix for absolute URLs.
 */
mxUrlConverter.prototype.baseDomain = null;

/**
 * Function: updateBaseUrl
 * 
 * Private helper function to update the base URL.
 */
mxUrlConverter.prototype.updateBaseUrl = function()
{
	this.baseDomain = location.protocol + '//' + location.host;
	this.baseUrl = this.baseDomain + location.pathname;
	var tmp = this.baseUrl.lastIndexOf('/');
	
	// Strips filename etc
	if (tmp > 0)
	{
		this.baseUrl = this.baseUrl.substring(0, tmp + 1);
	}
};

/**
 * Function: isEnabled
 * 
 * Returns .
 */
mxUrlConverter.prototype.isEnabled = function()
{
	return this.enabled;
};

/**
 * Function: setEnabled
 * 
 * Sets .
 */
mxUrlConverter.prototype.setEnabled = function(value)
{
	this.enabled = value;
};

/**
 * Function: getBaseUrl
 * 
 * Returns .
 */
mxUrlConverter.prototype.getBaseUrl = function()
{
	return this.baseUrl;
};

/**
 * Function: setBaseUrl
 * 
 * Sets .
 */
mxUrlConverter.prototype.setBaseUrl = function(value)
{
	this.baseUrl = value;
};

/**
 * Function: getBaseDomain
 * 
 * Returns .
 */
mxUrlConverter.prototype.getBaseDomain = function()
{
	return this.baseDomain;
},

/**
 * Function: setBaseDomain
 * 
 * Sets .
 */
mxUrlConverter.prototype.setBaseDomain = function(value)
{
	this.baseDomain = value;
},

/**
 * Function: isRelativeUrl
 * 
 * Returns true if the given URL is relative.
 */
mxUrlConverter.prototype.isRelativeUrl = function(url)
{
	return url != null && url.substring(0, 2) != '//' && url.substring(0, 7) != 'http://' &&
		url.substring(0, 8) != 'https://' && url.substring(0, 10) != 'data:image' &&
		url.substring(0, 7) != 'file://';
};

/**
 * Function: convert
 * 
 * Converts the given URL to an absolute URL with protol and domain.
 * Relative URLs are first converted to absolute URLs.
 */
mxUrlConverter.prototype.convert = function(url)
{
	if (this.isEnabled() && this.isRelativeUrl(url))
	{
		if (this.getBaseUrl() == null)
		{
			this.updateBaseUrl();
		}
		
		if (url.charAt(0) == '/')
		{
			url = this.getBaseDomain() + url;
		}
		else
		{
			url = this.getBaseUrl() + url;
		}
	}
	
	return url;
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPanningManager
 *
 * Implements a handler for panning.
 */
function mxPanningManager(graph)
{
	this.thread = null;
	this.active = false;
	this.tdx = 0;
	this.tdy = 0;
	this.t0x = 0;
	this.t0y = 0;
	this.dx = 0;
	this.dy = 0;
	this.scrollbars = false;
	this.scrollLeft = 0;
	this.scrollTop = 0;
	
	this.mouseListener =
	{
	    mouseDown: function(sender, me) { },
	    mouseMove: function(sender, me) { },
	    mouseUp: mxUtils.bind(this, function(sender, me)
	    {
	    	if (this.active)
	    	{
	    		this.stop();
	    	}
	    })
	};
	
	graph.addMouseListener(this.mouseListener);
	
	this.mouseUpListener = mxUtils.bind(this, function()
	{
	    	if (this.active)
	    	{
	    		this.stop();
	    	}
	});
	
	// Stops scrolling on every mouseup anywhere in the document
	mxEvent.addListener(document, 'mouseup', this.mouseUpListener);
	
	var createThread = mxUtils.bind(this, function()
	{
	    	this.scrollbars = mxUtils.hasScrollbars(graph.container);
	    	this.scrollLeft = graph.container.scrollLeft;
	    	this.scrollTop = graph.container.scrollTop;
	
	    	return window.setInterval(mxUtils.bind(this, function()
		{
			this.tdx -= this.dx;
			this.tdy -= this.dy;

			if (this.scrollbars)
			{
				var left = -graph.container.scrollLeft - Math.ceil(this.dx);
				var top = -graph.container.scrollTop - Math.ceil(this.dy);
				graph.panGraph(left, top);
				graph.panDx = this.scrollLeft - graph.container.scrollLeft;
				graph.panDy = this.scrollTop - graph.container.scrollTop;
				graph.fireEvent(new mxEventObject(mxEvent.PAN));
				// TODO: Implement graph.autoExtend
			}
			else
			{
				graph.panGraph(this.getDx(), this.getDy());
			}
		}), this.delay);
	});
	
	this.isActive = function()
	{
		return active;
	};
	
	this.getDx = function()
	{
		return Math.round(this.tdx);
	};
	
	this.getDy = function()
	{
		return Math.round(this.tdy);
	};
	
	this.start = function()
	{
		this.t0x = graph.view.translate.x;
		this.t0y = graph.view.translate.y;
		this.active = true;
	};
	
	this.panTo = function(x, y, w, h)
	{
		if (!this.active)
		{
			this.start();
		}
		
    	this.scrollLeft = graph.container.scrollLeft;
    	this.scrollTop = graph.container.scrollTop;
		
		w = (w != null) ? w : 0;
		h = (h != null) ? h : 0;
		
		var c = graph.container;
		this.dx = x + w - c.scrollLeft - c.clientWidth;
		
		if (this.dx < 0 && Math.abs(this.dx) < this.border)
		{
			this.dx = this.border + this.dx;
		}
		else if (this.handleMouseOut)
		{
			this.dx = Math.max(this.dx, 0);
		}
		else
		{
			this.dx = 0;
		}
		
		if (this.dx == 0)
		{
			this.dx = x - c.scrollLeft;
			
			if (this.dx > 0 && this.dx < this.border)
			{
				this.dx = this.dx - this.border;
			}
			else if (this.handleMouseOut)
			{
				this.dx = Math.min(0, this.dx);
			}
			else
			{
				this.dx = 0;
			}
		}
		
		this.dy = y + h - c.scrollTop - c.clientHeight;

		if (this.dy < 0 && Math.abs(this.dy) < this.border)
		{
			this.dy = this.border + this.dy;
		}
		else if (this.handleMouseOut)
		{
			this.dy = Math.max(this.dy, 0);
		}
		else
		{
			this.dy = 0;
		}
		
		if (this.dy == 0)
		{
			this.dy = y - c.scrollTop;
			
			if (this.dy > 0 && this.dy < this.border)
			{
				this.dy = this.dy - this.border;
			}
			else if (this.handleMouseOut)
			{
				this.dy = Math.min(0, this.dy);
			} 
			else
			{
				this.dy = 0;
			}
		}
		
		if (this.dx != 0 || this.dy != 0)
		{
			this.dx *= this.damper;
			this.dy *= this.damper;
			
			if (this.thread == null)
			{
				this.thread = createThread();
			}
		}
		else if (this.thread != null)
		{
			window.clearInterval(this.thread);
			this.thread = null;
		}
	};
	
	this.stop = function()
	{
		if (this.active)
		{
			this.active = false;
		
			if (this.thread != null)
	    	{
				window.clearInterval(this.thread);
				this.thread = null;
	    	}
			
			this.tdx = 0;
			this.tdy = 0;
			
			if (!this.scrollbars)
			{
				var px = graph.panDx;
				var py = graph.panDy;
		    	
		    	if (px != 0 || py != 0)
		    	{
		    		graph.panGraph(0, 0);
			    	graph.view.setTranslate(this.t0x + px / graph.view.scale, this.t0y + py / graph.view.scale);
		    	}
			}
			else
			{
				graph.panDx = 0;
				graph.panDy = 0;
				graph.fireEvent(new mxEventObject(mxEvent.PAN));
			}
		}
	};
	
	this.destroy = function()
	{
		graph.removeMouseListener(this.mouseListener);
		mxEvent.removeListener(document, 'mouseup', this.mouseUpListener);
	};
};

/**
 * Variable: damper
 * 
 * Damper value for the panning. Default is 1/6.
 */
mxPanningManager.prototype.damper = 1/6;

/**
 * Variable: delay
 * 
 * Delay in milliseconds for the panning. Default is 10.
 */
mxPanningManager.prototype.delay = 10;

/**
 * Variable: handleMouseOut
 * 
 * Specifies if mouse events outside of the component should be handled. Default is true. 
 */
mxPanningManager.prototype.handleMouseOut = true;

/**
 * Variable: border
 * 
 * Border to handle automatic panning inside the component. Default is 0 (disabled).
 */
mxPanningManager.prototype.border = 0;
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxPopupMenu
 * 
 * Basic popup menu. To add a vertical scrollbar to a given submenu, the
 * following code can be used.
 * 
 * (code)
 * var mxPopupMenuShowMenu = mxPopupMenu.prototype.showMenu;
 * mxPopupMenu.prototype.showMenu = function()
 * {
 *   mxPopupMenuShowMenu.apply(this, arguments);
 *   
 *   this.div.style.overflowY = 'auto';
 *   this.div.style.overflowX = 'hidden';
 *   this.div.style.maxHeight = '160px';
 * };
 * (end)
 * 
 * Constructor: mxPopupMenu
 * 
 * Constructs a popupmenu.
 * 
 * Event: mxEvent.SHOW
 *
 * Fires after the menu has been shown in .
 */
function mxPopupMenu(factoryMethod)
{
	this.factoryMethod = factoryMethod;
	
	if (factoryMethod != null)
	{
		this.init();
	}
};

/**
 * Extends mxEventSource.
 */
mxPopupMenu.prototype = new mxEventSource();
mxPopupMenu.prototype.constructor = mxPopupMenu;

/**
 * Variable: submenuImage
 * 
 * URL of the image to be used for the submenu icon.
 */
mxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + '/submenu.gif';

/**
 * Variable: zIndex
 * 
 * Specifies the zIndex for the popupmenu and its shadow. Default is 10006.
 */
mxPopupMenu.prototype.zIndex = 10006;

/**
 * Variable: factoryMethod
 * 
 * Function that is used to create the popup menu. The function takes the
 * current panning handler, the  under the mouse and the mouse
 * event that triggered the call as arguments.
 */
mxPopupMenu.prototype.factoryMethod = null;

/**
 * Variable: useLeftButtonForPopup
 * 
 * Specifies if popupmenus should be activated by clicking the left mouse
 * button. Default is false.
 */
mxPopupMenu.prototype.useLeftButtonForPopup = false;

/**
 * Variable: enabled
 * 
 * Specifies if events are handled. Default is true.
 */
mxPopupMenu.prototype.enabled = true;

/**
 * Variable: itemCount
 * 
 * Contains the number of times  has been called for a new menu.
 */
mxPopupMenu.prototype.itemCount = 0;

/**
 * Variable: autoExpand
 * 
 * Specifies if submenus should be expanded on mouseover. Default is false.
 */
mxPopupMenu.prototype.autoExpand = false;

/**
 * Variable: smartSeparators
 * 
 * Specifies if separators should only be added if a menu item follows them.
 * Default is false.
 */
mxPopupMenu.prototype.smartSeparators = false;

/**
 * Variable: labels
 * 
 * Specifies if any labels should be visible. Default is true.
 */
mxPopupMenu.prototype.labels = true;

/**
 * Function: init
 * 
 * Initializes the shapes required for this vertex handler.
 */
mxPopupMenu.prototype.init = function()
{
	// Adds the inner table
	this.table = document.createElement('table');
	this.table.className = 'mxPopupMenu';
	
	this.tbody = document.createElement('tbody');
	this.table.appendChild(this.tbody);

	// Adds the outer div
	this.div = document.createElement('div');
	this.div.className = 'mxPopupMenu';
	this.div.style.display = 'inline';
	this.div.style.zIndex = this.zIndex;
	this.div.appendChild(this.table);

	// Disables the context menu on the outer div
	mxEvent.disableContextMenu(this.div);
};

/**
 * Function: isEnabled
 * 
 * Returns true if events are handled. This implementation
 * returns .
 */
mxPopupMenu.prototype.isEnabled = function()
{
	return this.enabled;
};
	
/**
 * Function: setEnabled
 * 
 * Enables or disables event handling. This implementation
 * updates .
 */
mxPopupMenu.prototype.setEnabled = function(enabled)
{
	this.enabled = enabled;
};

/**
 * Function: isPopupTrigger
 * 
 * Returns true if the given event is a popupmenu trigger for the optional
 * given cell.
 * 
 * Parameters:
 * 
 * me -  that represents the mouse event.
 */
mxPopupMenu.prototype.isPopupTrigger = function(me)
{
	return me.isPopupTrigger() || (this.useLeftButtonForPopup && mxEvent.isLeftMouseButton(me.getEvent()));
};

/**
 * Function: addItem
 * 
 * Adds the given item to the given parent item. If no parent item is specified
 * then the item is added to the top-level menu. The return value may be used
 * as the parent argument, ie. as a submenu item. The return value is the table
 * row that represents the item.
 * 
 * Paramters:
 * 
 * title - String that represents the title of the menu item.
 * image - Optional URL for the image icon.
 * funct - Function associated that takes a mouseup or touchend event.
 * parent - Optional item returned by .
 * iconCls - Optional string that represents the CSS class for the image icon.
 * IconsCls is ignored if image is given.
 * enabled - Optional boolean indicating if the item is enabled. Default is true.
 * active - Optional boolean indicating if the menu should implement any event handling.
 * Default is true.
 */
mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled, active)
{
	parent = parent || this;
	this.itemCount++;
	
	// Smart separators only added if element contains items
	if (parent.willAddSeparator)
	{
		if (parent.containsItems)
		{
			this.addSeparator(parent, true);
		}

		parent.willAddSeparator = false;
	}

	parent.containsItems = true;
	var tr = document.createElement('tr');
	tr.className = 'mxPopupMenuItem';
	var col1 = document.createElement('td');
	col1.className = 'mxPopupMenuIcon';

	// Adds the given image into the first column
	if (image != null)
	{
		var img = document.createElement('img');
		img.src = image;
		col1.appendChild(img);
	}
	else if (iconCls != null)
	{
		var div = document.createElement('div');
		div.className = iconCls;
		col1.appendChild(div);
	}
	
	tr.appendChild(col1);
	
	if (this.labels)
	{
		var col2 = document.createElement('td');
		col2.className = 'mxPopupMenuItem' +
			((enabled != null && !enabled) ? ' mxDisabled' : '');
		
		mxUtils.write(col2, title);
		col2.align = 'left';
		tr.appendChild(col2);
	
		var col3 = document.createElement('td');
		col3.className = 'mxPopupMenuItem' +
			((enabled != null && !enabled) ? ' mxDisabled' : '');
		col3.style.paddingRight = '6px';
		col3.style.textAlign = 'right';
		
		tr.appendChild(col3);
		
		if (parent.div == null)
		{
			this.createSubmenu(parent);
		}
	}
	
	parent.tbody.appendChild(tr);

	if (active != false && enabled != false)
	{
		var currentSelection = null;
		
		mxEvent.addGestureListeners(tr,
			mxUtils.bind(this, function(evt)
			{
				this.eventReceiver = tr;
				
				if (parent.activeRow != tr && parent.activeRow != parent)
				{
					if (parent.activeRow != null && parent.activeRow.div.parentNode != null)
					{
						this.hideSubmenu(parent);
					}
					
					if (tr.div != null)
					{
						this.showSubmenu(parent, tr);
						parent.activeRow = tr;
					}
				}
				
				// Workaround for lost current selection in page because of focus in IE
				if (document.selection != null && (mxClient.IS_QUIRKS || document.documentMode == 8))
				{
					currentSelection = document.selection.createRange();
				}
				
				mxEvent.consume(evt);
			}),
			mxUtils.bind(this, function(evt)
			{
				if (parent.activeRow != tr && parent.activeRow != parent)
				{
					if (parent.activeRow != null && parent.activeRow.div.parentNode != null)
					{
						this.hideSubmenu(parent);
					}
					
					if (this.autoExpand && tr.div != null)
					{
						this.showSubmenu(parent, tr);
						parent.activeRow = tr;
					}
				}
		
				// Sets hover style because TR in IE doesn't have hover
				tr.className = 'mxPopupMenuItemHover';
			}),
			mxUtils.bind(this, function(evt)
			{
				// EventReceiver avoids clicks on a submenu item
				// which has just been shown in the mousedown
				if (this.eventReceiver == tr)
				{
					if (parent.activeRow != tr)
					{
						this.hideMenu();
					}
					
					// Workaround for lost current selection in page because of focus in IE
					if (currentSelection != null)
					{
						// Workaround for "unspecified error" in IE8 standards
						try
						{
							currentSelection.select();
						}
						catch (e)
						{
							// ignore
						}

						currentSelection = null;
					}
					
					if (funct != null)
					{
						funct(evt);
					}
				}
				
				this.eventReceiver = null;
				mxEvent.consume(evt);
			})
		);
	
		// Resets hover style because TR in IE doesn't have hover
		mxEvent.addListener(tr, 'mouseout',
			mxUtils.bind(this, function(evt)
			{
				tr.className = 'mxPopupMenuItem';
			})
		);
	}
	
	return tr;
};

/**
 * Adds a checkmark to the given menuitem.
 */
mxPopupMenu.prototype.addCheckmark = function(item, img)
{
	var td = item.firstChild.nextSibling;
	td.style.backgroundImage = 'url(\'' + img + '\')';
	td.style.backgroundRepeat = 'no-repeat';
	td.style.backgroundPosition = '2px 50%';
};

/**
 * Function: createSubmenu
 * 
 * Creates the nodes required to add submenu items inside the given parent
 * item. This is called in  if a parent item is used for the first
 * time. This adds various DOM nodes and a  to the parent.
 * 
 * Parameters:
 * 
 * parent - An item returned by .
 */
mxPopupMenu.prototype.createSubmenu = function(parent)
{
	parent.table = document.createElement('table');
	parent.table.className = 'mxPopupMenu';

	parent.tbody = document.createElement('tbody');
	parent.table.appendChild(parent.tbody);

	parent.div = document.createElement('div');
	parent.div.className = 'mxPopupMenu';

	parent.div.style.position = 'absolute';
	parent.div.style.display = 'inline';
	parent.div.style.zIndex = this.zIndex;
	
	parent.div.appendChild(parent.table);
	
	var img = document.createElement('img');
	img.setAttribute('src', this.submenuImage);
	
	// Last column of the submenu item in the parent menu
	td = parent.firstChild.nextSibling.nextSibling;
	td.appendChild(img);
};

/**
 * Function: showSubmenu
 * 
 * Shows the submenu inside the given parent row.
 */
mxPopupMenu.prototype.showSubmenu = function(parent, row)
{
	if (row.div != null)
	{
		row.div.style.left = (parent.div.offsetLeft +
			row.offsetLeft+row.offsetWidth - 1) + 'px';
		row.div.style.top = (parent.div.offsetTop+row.offsetTop) + 'px';
		document.body.appendChild(row.div);
		
		// Moves the submenu to the left side if there is no space
		var left = parseInt(row.div.offsetLeft);
		var width = parseInt(row.div.offsetWidth);
		var offset = mxUtils.getDocumentScrollOrigin(document);
		
		var b = document.body;
		var d = document.documentElement;
		
		var right = offset.x + (b.clientWidth || d.clientWidth);
		
		if (left + width > right)
		{
			row.div.style.left = Math.max(0, (parent.div.offsetLeft - width + ((mxClient.IS_IE) ? 6 : -6))) + 'px';
		}
		
		mxUtils.fit(row.div);
	}
};

/**
 * Function: addSeparator
 * 
 * Adds a horizontal separator in the given parent item or the top-level menu
 * if no parent is specified.
 * 
 * Parameters:
 * 
 * parent - Optional item returned by .
 * force - Optional boolean to ignore . Default is false.
 */
mxPopupMenu.prototype.addSeparator = function(parent, force)
{
	parent = parent || this;
	
	if (this.smartSeparators && !force)
	{
		parent.willAddSeparator = true;
	}
	else if (parent.tbody != null)
	{
		parent.willAddSeparator = false;
		var tr = document.createElement('tr');
		
		var col1 = document.createElement('td');
		col1.className = 'mxPopupMenuIcon';
		col1.style.padding = '0 0 0 0px';
		
		tr.appendChild(col1);
		
		var col2 = document.createElement('td');
		col2.style.padding = '0 0 0 0px';
		col2.setAttribute('colSpan', '2');
	
		var hr = document.createElement('hr');
		hr.setAttribute('size', '1');
		col2.appendChild(hr);
		
		tr.appendChild(col2);
		
		parent.tbody.appendChild(tr);
	}
};

/**
 * Function: popup
 * 
 * Shows the popup menu for the given event and cell.
 * 
 * Example:
 * 
 * (code)
 * graph.panningHandler.popup = function(x, y, cell, evt)
 * {
 *   mxUtils.alert('Hello, World!');
 * }
 * (end)
 */
mxPopupMenu.prototype.popup = function(x, y, cell, evt)
{
	if (this.div != null && this.tbody != null && this.factoryMethod != null)
	{
		this.div.style.left = x + 'px';
		this.div.style.top = y + 'px';
		
		// Removes all child nodes from the existing menu
		while (this.tbody.firstChild != null)
		{
			mxEvent.release(this.tbody.firstChild);
			this.tbody.removeChild(this.tbody.firstChild);
		}
		
		this.itemCount = 0;
		this.factoryMethod(this, cell, evt);
		
		if (this.itemCount > 0)
		{
			this.showMenu();
			this.fireEvent(new mxEventObject(mxEvent.SHOW));
		}
	}
};

/**
 * Function: isMenuShowing
 * 
 * Returns true if the menu is showing.
 */
mxPopupMenu.prototype.isMenuShowing = function()
{
	return this.div != null && this.div.parentNode == document.body;
};

/**
 * Function: showMenu
 * 
 * Shows the menu.
 */
mxPopupMenu.prototype.showMenu = function()
{
	// Disables filter-based shadow in IE9 standards mode
	if (document.documentMode >= 9)
	{
		this.div.style.filter = 'none';
	}
	
	// Fits the div inside the viewport
	document.body.appendChild(this.div);
	mxUtils.fit(this.div);
};

/**
 * Function: hideMenu
 * 
 * Removes the menu and all submenus.
 */
mxPopupMenu.prototype.hideMenu = function()
{
	if (this.div != null)
	{
		if (this.div.parentNode != null)
		{
			this.div.parentNode.removeChild(this.div);
		}
		
		this.hideSubmenu(this);
		this.containsItems = false;
		this.fireEvent(new mxEventObject(mxEvent.HIDE));
	}
};

/**
 * Function: hideSubmenu
 * 
 * Removes all submenus inside the given parent.
 * 
 * Parameters:
 * 
 * parent - An item returned by .
 */
mxPopupMenu.prototype.hideSubmenu = function(parent)
{
	if (parent.activeRow != null)
	{
		this.hideSubmenu(parent.activeRow);
		
		if (parent.activeRow.div.parentNode != null)
		{
			parent.activeRow.div.parentNode.removeChild(parent.activeRow.div);
		}
		
		parent.activeRow = null;
	}
};

/**
 * Function: destroy
 * 
 * Destroys the handler and all its resources and DOM nodes.
 */
mxPopupMenu.prototype.destroy = function()
{
	if (this.div != null)
	{
		mxEvent.release(this.div);
		
		if (this.div.parentNode != null)
		{
			this.div.parentNode.removeChild(this.div);
		}
		
		this.div = null;
	}
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxAutoSaveManager
 * 
 * Manager for automatically saving diagrams. The  hook must be
 * implemented.
 * 
 * Example:
 * 
 * (code)
 * var mgr = new mxAutoSaveManager(editor.graph);
 * mgr.save = function()
 * {
 *   mxLog.show();
 *   mxLog.debug('save');
 * };
 * (end)
 * 
 * Constructor: mxAutoSaveManager
 *
 * Constructs a new automatic layout for the given graph.
 *
 * Arguments:
 * 
 * graph - Reference to the enclosing graph. 
 */
function mxAutoSaveManager(graph)
{
	// Notifies the manager of a change
	this.changeHandler = mxUtils.bind(this, function(sender, evt)
	{
		if (this.isEnabled())
		{
			this.graphModelChanged(evt.getProperty('edit').changes);
		}
	});

	this.setGraph(graph);
};

/**
 * Extends mxEventSource.
 */
mxAutoSaveManager.prototype = new mxEventSource();
mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;

/**
 * Variable: graph
 * 
 * Reference to the enclosing .
 */
mxAutoSaveManager.prototype.graph = null;

/**
 * Variable: autoSaveDelay
 * 
 * Minimum amount of seconds between two consecutive autosaves. Eg. a
 * value of 1 (s) means the graph is not stored more than once per second.
 * Default is 10.
 */
mxAutoSaveManager.prototype.autoSaveDelay = 10;

/**
 * Variable: autoSaveThrottle
 * 
 * Minimum amount of seconds between two consecutive autosaves triggered by
 * more than  changes within a timespan of less than
 *  seconds. Eg. a value of 1 (s) means the graph is not
 * stored more than once per second even if there are more than
 *  changes within that timespan. Default is 2.
 */
mxAutoSaveManager.prototype.autoSaveThrottle = 2;

/**
 * Variable: autoSaveThreshold
 * 
 * Minimum amount of ignored changes before an autosave. Eg. a value of 2
 * means after 2 change of the graph model the autosave will trigger if the
 * condition below is true. Default is 5.
 */
mxAutoSaveManager.prototype.autoSaveThreshold = 5;

/**
 * Variable: ignoredChanges
 * 
 * Counter for ignored changes in autosave.
 */
mxAutoSaveManager.prototype.ignoredChanges = 0;

/**
 * Variable: lastSnapshot
 * 
 * Used for autosaving. See .
 */
mxAutoSaveManager.prototype.lastSnapshot = 0;

/**
 * Variable: enabled
 * 
 * Specifies if event handling is enabled. Default is true.
 */
mxAutoSaveManager.prototype.enabled = true;

/**
 * Variable: changeHandler
 * 
 * Holds the function that handles graph model changes.
 */
mxAutoSaveManager.prototype.changeHandler = null;

/**
 * Function: isEnabled
 * 
 * Returns true if events are handled. This implementation
 * returns .
 */
mxAutoSaveManager.prototype.isEnabled = function()
{
	return this.enabled;
};

/**
 * Function: setEnabled
 * 
 * Enables or disables event handling. This implementation
 * updates .
 * 
 * Parameters:
 * 
 * enabled - Boolean that specifies the new enabled state.
 */
mxAutoSaveManager.prototype.setEnabled = function(value)
{
	this.enabled = value;
};

/**
 * Function: setGraph
 * 
 * Sets the graph that the layouts operate on.
 */
mxAutoSaveManager.prototype.setGraph = function(graph)
{
	if (this.graph != null)
	{
		this.graph.getModel().removeListener(this.changeHandler);
	}
	
	this.graph = graph;
	
	if (this.graph != null)
	{
		this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
	}
};

/**
 * Function: save
 * 
 * Empty hook that is called if the graph should be saved.
 */
mxAutoSaveManager.prototype.save = function()
{
	// empty
};

/**
 * Function: graphModelChanged
 * 
 * Invoked when the graph model has changed.
 */
mxAutoSaveManager.prototype.graphModelChanged = function(changes)
{
	var now = new Date().getTime();
	var dt = (now - this.lastSnapshot) / 1000;
	
	if (dt > this.autoSaveDelay ||
		(this.ignoredChanges >= this.autoSaveThreshold &&
		 dt > this.autoSaveThrottle))
	{
		this.save();
		this.reset();
	}
	else
	{
		// Increments the number of ignored changes
		this.ignoredChanges++;
	}
};

/**
 * Function: reset
 * 
 * Resets all counters.
 */
mxAutoSaveManager.prototype.reset = function()
{
	this.lastSnapshot = new Date().getTime();
	this.ignoredChanges = 0;
};

/**
 * Function: destroy
 * 
 * Removes all handlers from the  and deletes the reference to it.
 */
mxAutoSaveManager.prototype.destroy = function()
{
	this.setGraph(null);
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 *
 * Class: mxAnimation
 * 
 * Implements a basic animation in JavaScript.
 * 
 * Constructor: mxAnimation
 * 
 * Constructs an animation.
 * 
 * Parameters:
 * 
 * graph - Reference to the enclosing .
 */
function mxAnimation(delay)
{
	this.delay = (delay != null) ? delay : 20;
};

/**
 * Extends mxEventSource.
 */
mxAnimation.prototype = new mxEventSource();
mxAnimation.prototype.constructor = mxAnimation;

/**
 * Variable: delay
 * 
 * Specifies the delay between the animation steps. Defaul is 30ms.
 */
mxAnimation.prototype.delay = null;

/**
 * Variable: thread
 * 
 * Reference to the thread while the animation is running.
 */
mxAnimation.prototype.thread = null;

/**
 * Function: isRunning
 * 
 * Returns true if the animation is running.
 */
mxAnimation.prototype.isRunning = function()
{
	return this.thread != null;
};

/**
 * Function: startAnimation
 *
 * Starts the animation by repeatedly invoking updateAnimation.
 */
mxAnimation.prototype.startAnimation = function()
{
	if (this.thread == null)
	{
		this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay);
	}
};

/**
 * Function: updateAnimation
 *
 * Hook for subclassers to implement the animation. Invoke stopAnimation
 * when finished, startAnimation to resume. This is called whenever the
 * timer fires and fires an mxEvent.EXECUTE event with no properties.
 */
mxAnimation.prototype.updateAnimation = function()
{
	this.fireEvent(new mxEventObject(mxEvent.EXECUTE));
};

/**
 * Function: stopAnimation
 *
 * Stops the animation by deleting the timer and fires an .
 */
mxAnimation.prototype.stopAnimation = function()
{
	if (this.thread != null)
	{
		window.clearInterval(this.thread);
		this.thread = null;
		this.fireEvent(new mxEventObject(mxEvent.DONE));
	}
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 *
 * Class: mxMorphing
 * 
 * Implements animation for morphing cells. Here is an example of
 * using this class for animating the result of a layout algorithm:
 * 
 * (code)
 * graph.getModel().beginUpdate();
 * try
 * {
 *   var circleLayout = new mxCircleLayout(graph);
 *   circleLayout.execute(graph.getDefaultParent());
 * }
 * finally
 * {
 *   var morph = new mxMorphing(graph);
 *   morph.addListener(mxEvent.DONE, function()
 *   {
 *     graph.getModel().endUpdate();
 *   });
 *   
 *   morph.startAnimation();
 * }
 * (end)
 * 
 * Constructor: mxMorphing
 * 
 * Constructs an animation.
 * 
 * Parameters:
 * 
 * graph - Reference to the enclosing .
 * steps - Optional number of steps in the morphing animation. Default is 6.
 * ease - Optional easing constant for the animation. Default is 1.5.
 * delay - Optional delay between the animation steps. Passed to .
 */
function mxMorphing(graph, steps, ease, delay)
{
	mxAnimation.call(this, delay);
	this.graph = graph;
	this.steps = (steps != null) ? steps : 6;
	this.ease = (ease != null) ? ease : 1.5;
};

/**
 * Extends mxEventSource.
 */
mxMorphing.prototype = new mxAnimation();
mxMorphing.prototype.constructor = mxMorphing;

/**
 * Variable: graph
 * 
 * Specifies the delay between the animation steps. Defaul is 30ms.
 */
mxMorphing.prototype.graph = null;

/**
 * Variable: steps
 * 
 * Specifies the maximum number of steps for the morphing.
 */
mxMorphing.prototype.steps = null;

/**
 * Variable: step
 * 
 * Contains the current step.
 */
mxMorphing.prototype.step = 0;

/**
 * Variable: ease
 * 
 * Ease-off for movement towards the given vector. Larger values are
 * slower and smoother. Default is 4.
 */
mxMorphing.prototype.ease = null;

/**
 * Variable: cells
 * 
 * Optional array of cells to be animated. If this is not specified
 * then all cells are checked and animated if they have been moved
 * in the current transaction.
 */
mxMorphing.prototype.cells = null;

/**
 * Function: updateAnimation
 *
 * Animation step.
 */
mxMorphing.prototype.updateAnimation = function()
{
	mxAnimation.prototype.updateAnimation.apply(this, arguments);
	var move = new mxCellStatePreview(this.graph);

	if (this.cells != null)
	{
		// Animates the given cells individually without recursion
		for (var i = 0; i < this.cells.length; i++)
		{
			this.animateCell(this.cells[i], move, false);
		}
	}
	else
	{
		// Animates all changed cells by using recursion to find
		// the changed cells but not for the animation itself
		this.animateCell(this.graph.getModel().getRoot(), move, true);
	}
	
	this.show(move);
	
	if (move.isEmpty() || this.step++ >= this.steps)
	{
		this.stopAnimation();
	}
};

/**
 * Function: show
 *
 * Shows the changes in the given .
 */
mxMorphing.prototype.show = function(move)
{
	move.show();
};

/**
 * Function: animateCell
 *
 * Animates the given cell state using .
 */
mxMorphing.prototype.animateCell = function(cell, move, recurse)
{
	var state = this.graph.getView().getState(cell);
	var delta = null;

	if (state != null)
	{
		// Moves the animated state from where it will be after the model
		// change by subtracting the given delta vector from that location
		delta = this.getDelta(state);

		if (this.graph.getModel().isVertex(cell) && (delta.x != 0 || delta.y != 0))
		{
			var translate = this.graph.view.getTranslate();
			var scale = this.graph.view.getScale();
			
			delta.x += translate.x * scale;
			delta.y += translate.y * scale;
			
			move.moveState(state, -delta.x / this.ease, -delta.y / this.ease);
		}
	}
	
	if (recurse && !this.stopRecursion(state, delta))
	{
		var childCount = this.graph.getModel().getChildCount(cell);

		for (var i = 0; i < childCount; i++)
		{
			this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse);
		}
	}
};

/**
 * Function: stopRecursion
 *
 * Returns true if the animation should not recursively find more
 * deltas for children if the given parent state has been animated.
 */
mxMorphing.prototype.stopRecursion = function(state, delta)
{
	return delta != null && (delta.x != 0 || delta.y != 0);
};

/**
 * Function: getDelta
 *
 * Returns the vector between the current rendered state and the future
 * location of the state after the display will be updated.
 */
mxMorphing.prototype.getDelta = function(state)
{
	var origin = this.getOriginForCell(state.cell);
	var translate = this.graph.getView().getTranslate();
	var scale = this.graph.getView().getScale();
	var x = state.x / scale - translate.x;
	var y = state.y / scale - translate.y;

	return new mxPoint((origin.x - x) * scale, (origin.y - y) * scale);
};

/**
 * Function: getOriginForCell
 *
 * Returns the top, left corner of the given cell. TODO: Improve performance
 * by using caching inside this method as the result per cell never changes
 * during the lifecycle of this object.
 */
mxMorphing.prototype.getOriginForCell = function(cell)
{
	var result = null;
	
	if (cell != null)
	{
		var parent = this.graph.getModel().getParent(cell);
		var geo = this.graph.getCellGeometry(cell);
		result = this.getOriginForCell(parent);
		
		// TODO: Handle offsets
		if (geo != null)
		{
			if (geo.relative)
			{
				var pgeo = this.graph.getCellGeometry(parent);
				
				if (pgeo != null)
				{
					result.x += geo.x * pgeo.width;
					result.y += geo.y * pgeo.height;
				}
			}
			else
			{
				result.x += geo.x;
				result.y += geo.y;
			}
		}
	}
	
	if (result == null)
	{
		var t = this.graph.view.getTranslate();
		result = new mxPoint(-t.x, -t.y);
	}
	
	return result;
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxImageBundle
 *
 * Maps from keys to base64 encoded images or file locations. All values must
 * be URLs or use the format data:image/format followed by a comma and the base64
 * encoded image data, eg. "data:image/gif,XYZ", where XYZ is the base64 encoded
 * image data.
 * 
 * To add a new image bundle to an existing graph, the following code is used:
 * 
 * (code)
 * var bundle = new mxImageBundle(alt);
 * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' +
 *   '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' +
 *   'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' +
 *   'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback);
 * bundle.putImage('mySvgImage', 'data:image/svg+xml,' + encodeURIComponent(
 *   '' +
 *   '' +
 *   '' +
 *   ''), fallback);
 * graph.addImageBundle(bundle);
 * (end);
 * 
 * Alt is an optional boolean (default is false) that specifies if the value
 * or the fallback should be returned in .
 * 
 * The image can then be referenced in any cell style using image=myImage.
 * If you are using mxOutline, you should use the same image bundles in the
 * graph that renders the outline.
 * 
 * The keys for images are resolved in  and
 * turned into a data URI if the returned value has a short data URI format
 * as specified above.
 * 
 * A typical value for the fallback is a MTHML link as defined in RFC 2557.
 * Note that this format requires a file to be dynamically created on the
 * server-side, or the page that contains the graph to be modified to contain
 * the resources, this can be done by adding a comment that contains the
 * resource in the HEAD section of the page after the title tag.
 * 
 * This type of fallback mechanism should be used in IE6 and IE7. IE8 does
 * support data URIs, but the maximum size is limited to 32 KB, which means
 * all data URIs should be limited to 32 KB.
 */
function mxImageBundle(alt)
{
	this.images = [];
	this.alt = (alt != null) ? alt : false;
};

/**
 * Variable: images
 * 
 * Maps from keys to images.
 */
mxImageBundle.prototype.images = null;

/**
 * Variable: alt
 * 
 * Specifies if the fallback representation should be returned.
 */
mxImageBundle.prototype.images = null;

/**
 * Function: putImage
 * 
 * Adds the specified entry to the map. The entry is an object with a value and
 * fallback property as specified in the arguments.
 */
mxImageBundle.prototype.putImage = function(key, value, fallback)
{
	this.images[key] = {value: value, fallback: fallback};
};

/**
 * Function: getImage
 * 
 * Returns the value for the given key. This returns the value
 * or fallback, depending on . The fallback is returned if
 *  is true, the value is returned otherwise.
 */
mxImageBundle.prototype.getImage = function(key)
{
	var result = null;
	
	if (key != null)
	{
		var img = this.images[key];
		
		if (img != null)
		{
			result = (this.alt) ? img.fallback : img.value;
		}
	}
	
	return result;
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxImageExport
 * 
 * Creates a new image export instance to be used with an export canvas. Here
 * is an example that uses this class to create an image via a backend using
 * .
 * 
 * (code)
 * var xmlDoc = mxUtils.createXmlDocument();
 * var root = xmlDoc.createElement('output');
 * xmlDoc.appendChild(root);
 * 
 * var xmlCanvas = new mxXmlCanvas2D(root);
 * var imgExport = new mxImageExport();
 * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
 * 
 * var bounds = graph.getGraphBounds();
 * var w = Math.ceil(bounds.x + bounds.width);
 * var h = Math.ceil(bounds.y + bounds.height);
 * 
 * var xml = mxUtils.getXml(root);
 * new mxXmlRequest('export', 'format=png&w=' + w +
 * 		'&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))
 * 		.simulate(document, '_blank');
 * (end)
 * 
 * Constructor: mxImageExport
 * 
 * Constructs a new image export.
 */
function mxImageExport() { };

/**
 * Variable: includeOverlays
 * 
 * Specifies if overlays should be included in the export. Default is false.
 */
mxImageExport.prototype.includeOverlays = false;

/**
 * Function: drawState
 * 
 * Draws the given state and all its descendants to the given canvas.
 */
mxImageExport.prototype.drawState = function(state, canvas)
{
	if (state != null)
	{
		this.visitStatesRecursive(state, canvas, mxUtils.bind(this, function()
		{
			this.drawCellState.apply(this, arguments);
		}));
				
		// Paints the overlays
		if (this.includeOverlays)
		{
			this.visitStatesRecursive(state, canvas, mxUtils.bind(this, function()
			{
				this.drawOverlays.apply(this, arguments);
			}));
		}
	}
};

/**
 * Function: drawState
 * 
 * Draws the given state and all its descendants to the given canvas.
 */
mxImageExport.prototype.visitStatesRecursive = function(state, canvas, visitor)
{
	if (state != null)
	{
		visitor(state, canvas);
		
		var graph = state.view.graph;
		var childCount = graph.model.getChildCount(state.cell);
		
		for (var i = 0; i < childCount; i++)
		{
			var childState = graph.view.getState(graph.model.getChildAt(state.cell, i));
			this.visitStatesRecursive(childState, canvas, visitor);
		}
	}
};

/**
 * Function: getLinkForCellState
 * 
 * Returns the link for the given cell state and canvas. This returns null.
 */
mxImageExport.prototype.getLinkForCellState = function(state, canvas)
{
	return null;
};

/**
 * Function: drawCellState
 * 
 * Draws the given state to the given canvas.
 */
mxImageExport.prototype.drawCellState = function(state, canvas)
{
	// Experimental feature
	var link = this.getLinkForCellState(state, canvas);
	
	if (link != null)
	{
		canvas.setLink(link);
	}
	
	// Paints the shape and text
	this.drawShape(state, canvas);
	this.drawText(state, canvas);

	if (link != null)
	{
		canvas.setLink(null);
	}
};

/**
 * Function: drawShape
 * 
 * Draws the shape of the given state.
 */
mxImageExport.prototype.drawShape = function(state, canvas)
{
	if (state.shape instanceof mxShape && state.shape.checkBounds())
	{
		canvas.save();
		state.shape.paint(canvas);
		canvas.restore();
	}
};

/**
 * Function: drawText
 * 
 * Draws the text of the given state.
 */
mxImageExport.prototype.drawText = function(state, canvas)
{
	if (state.text != null && state.text.checkBounds())
	{
		canvas.save();
		state.text.paint(canvas);
		canvas.restore();
	}
};

/**
 * Function: drawOverlays
 * 
 * Draws the overlays for the given state. This is called if 
 * is true.
 */
mxImageExport.prototype.drawOverlays = function(state, canvas)
{
	if (state.overlays != null)
	{
		state.overlays.visit(function(id, shape)
		{
			if (shape instanceof mxShape)
			{
				shape.paint(canvas);
			}
		});
	}
};

/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxAbstractCanvas2D
 *
 * Base class for all canvases. A description of the public API is available in .
 * All color values of  will be converted to null in the state.
 * 
 * Constructor: mxAbstractCanvas2D
 *
 * Constructs a new abstract canvas.
 */
function mxAbstractCanvas2D()
{
	/**
	 * Variable: converter
	 * 
	 * Holds the  to convert image URLs.
	 */
	this.converter = this.createUrlConverter();
	
	this.reset();
};

/**
 * Variable: state
 * 
 * Holds the current state.
 */
mxAbstractCanvas2D.prototype.state = null;

/**
 * Variable: states
 * 
 * Stack of states.
 */
mxAbstractCanvas2D.prototype.states = null;

/**
 * Variable: path
 * 
 * Holds the current path as an array.
 */
mxAbstractCanvas2D.prototype.path = null;

/**
 * Variable: rotateHtml
 * 
 * Switch for rotation of HTML. Default is false.
 */
mxAbstractCanvas2D.prototype.rotateHtml = true;

/**
 * Variable: lastX
 * 
 * Holds the last x coordinate.
 */
mxAbstractCanvas2D.prototype.lastX = 0;

/**
 * Variable: lastY
 * 
 * Holds the last y coordinate.
 */
mxAbstractCanvas2D.prototype.lastY = 0;

/**
 * Variable: moveOp
 * 
 * Contains the string used for moving in paths. Default is 'M'.
 */
mxAbstractCanvas2D.prototype.moveOp = 'M';

/**
 * Variable: lineOp
 * 
 * Contains the string used for moving in paths. Default is 'L'.
 */
mxAbstractCanvas2D.prototype.lineOp = 'L';

/**
 * Variable: quadOp
 * 
 * Contains the string used for quadratic paths. Default is 'Q'.
 */
mxAbstractCanvas2D.prototype.quadOp = 'Q';

/**
 * Variable: curveOp
 * 
 * Contains the string used for bezier curves. Default is 'C'.
 */
mxAbstractCanvas2D.prototype.curveOp = 'C';

/**
 * Variable: closeOp
 * 
 * Holds the operator for closing curves. Default is 'Z'.
 */
mxAbstractCanvas2D.prototype.closeOp = 'Z';

/**
 * Variable: pointerEvents
 * 
 * Boolean value that specifies if events should be handled. Default is false.
 */
mxAbstractCanvas2D.prototype.pointerEvents = false;

/**
 * Function: createUrlConverter
 * 
 * Create a new  and returns it.
 */
mxAbstractCanvas2D.prototype.createUrlConverter = function()
{
	return new mxUrlConverter();
};

/**
 * Function: reset
 * 
 * Resets the state of this canvas.
 */
mxAbstractCanvas2D.prototype.reset = function()
{
	this.state = this.createState();
	this.states = [];
};

/**
 * Function: createState
 * 
 * Creates the state of the this canvas.
 */
mxAbstractCanvas2D.prototype.createState = function()
{
	return {
		dx: 0,
		dy: 0,
		scale: 1,
		alpha: 1,
		fillAlpha: 1,
		strokeAlpha: 1,
		fillColor: null,
		gradientFillAlpha: 1,
		gradientColor: null,
		gradientAlpha: 1,
		gradientDirection: null,
		strokeColor: null,
		strokeWidth: 1,
		dashed: false,
		dashPattern: '3 3',
		fixDash: false,
		lineCap: 'flat',
		lineJoin: 'miter',
		miterLimit: 10,
		fontColor: '#000000',
		fontBackgroundColor: null,
		fontBorderColor: null,
		fontSize: mxConstants.DEFAULT_FONTSIZE,
		fontFamily: mxConstants.DEFAULT_FONTFAMILY,
		fontStyle: 0,
		shadow: false,
		shadowColor: mxConstants.SHADOWCOLOR,
		shadowAlpha: mxConstants.SHADOW_OPACITY,
		shadowDx: mxConstants.SHADOW_OFFSET_X,
		shadowDy: mxConstants.SHADOW_OFFSET_Y,
		rotation: 0,
		rotationCx: 0,
		rotationCy: 0
	};
};

/**
 * Function: format
 * 
 * Rounds all numbers to integers.
 */
mxAbstractCanvas2D.prototype.format = function(value)
{
	return Math.round(parseFloat(value));
};

/**
 * Function: addOp
 * 
 * Adds the given operation to the path.
 */
mxAbstractCanvas2D.prototype.addOp = function()
{
	if (this.path != null)
	{
		this.path.push(arguments[0]);
		
		if (arguments.length > 2)
		{
			var s = this.state;

			for (var i = 2; i < arguments.length; i += 2)
			{
				this.lastX = arguments[i - 1];
				this.lastY = arguments[i];
				
				this.path.push(this.format((this.lastX + s.dx) * s.scale));
				this.path.push(this.format((this.lastY + s.dy) * s.scale));
			}
		}
	}
};

/**
 * Function: rotatePoint
 * 
 * Rotates the given point and returns the result as an .
 */
mxAbstractCanvas2D.prototype.rotatePoint = function(x, y, theta, cx, cy)
{
	var rad = theta * (Math.PI / 180);
	
	return mxUtils.getRotatedPoint(new mxPoint(x, y), Math.cos(rad),
		Math.sin(rad), new mxPoint(cx, cy));
};

/**
 * Function: save
 * 
 * Saves the current state.
 */
mxAbstractCanvas2D.prototype.save = function()
{
	this.states.push(this.state);
	this.state = mxUtils.clone(this.state);
};

/**
 * Function: restore
 * 
 * Restores the current state.
 */
mxAbstractCanvas2D.prototype.restore = function()
{
	if (this.states.length > 0)
	{
		this.state = this.states.pop();
	}
};

/**
 * Function: setLink
 * 
 * Sets the current link. Hook for subclassers.
 */
mxAbstractCanvas2D.prototype.setLink = function(link)
{
	// nop
};

/**
 * Function: scale
 * 
 * Scales the current state.
 */
mxAbstractCanvas2D.prototype.scale = function(value)
{
	this.state.scale *= value;
	this.state.strokeWidth *= value;
};

/**
 * Function: translate
 * 
 * Translates the current state.
 */
mxAbstractCanvas2D.prototype.translate = function(dx, dy)
{
	this.state.dx += dx;
	this.state.dy += dy;
};

/**
 * Function: rotate
 * 
 * Rotates the current state.
 */
mxAbstractCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
{
	// nop
};

/**
 * Function: setAlpha
 * 
 * Sets the current alpha.
 */
mxAbstractCanvas2D.prototype.setAlpha = function(value)
{
	this.state.alpha = value;
};

/**
 * Function: setFillAlpha
 * 
 * Sets the current solid fill alpha.
 */
mxAbstractCanvas2D.prototype.setFillAlpha = function(value)
{
	this.state.fillAlpha = value;
};

/**
 * Function: setStrokeAlpha
 * 
 * Sets the current stroke alpha.
 */
mxAbstractCanvas2D.prototype.setStrokeAlpha = function(value)
{
	this.state.strokeAlpha = value;
};

/**
 * Function: setFillColor
 * 
 * Sets the current fill color.
 */
mxAbstractCanvas2D.prototype.setFillColor = function(value)
{
	if (value == mxConstants.NONE)
	{
		value = null;
	}
	
	this.state.fillColor = value;
	this.state.gradientColor = null;
};

/**
 * Function: setGradient
 * 
 * Sets the current gradient.
 */
mxAbstractCanvas2D.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)
{
	var s = this.state;
	s.fillColor = color1;
	s.gradientFillAlpha = (alpha1 != null) ? alpha1 : 1;
	s.gradientColor = color2;
	s.gradientAlpha = (alpha2 != null) ? alpha2 : 1;
	s.gradientDirection = direction;
};

/**
 * Function: setStrokeColor
 * 
 * Sets the current stroke color.
 */
mxAbstractCanvas2D.prototype.setStrokeColor = function(value)
{
	if (value == mxConstants.NONE)
	{
		value = null;
	}
	
	this.state.strokeColor = value;
};

/**
 * Function: setStrokeWidth
 * 
 * Sets the current stroke width.
 */
mxAbstractCanvas2D.prototype.setStrokeWidth = function(value)
{
	this.state.strokeWidth = value;
};

/**
 * Function: setDashed
 * 
 * Enables or disables dashed lines.
 */
mxAbstractCanvas2D.prototype.setDashed = function(value, fixDash)
{
	this.state.dashed = value;
	this.state.fixDash = fixDash;
};

/**
 * Function: setDashPattern
 * 
 * Sets the current dash pattern.
 */
mxAbstractCanvas2D.prototype.setDashPattern = function(value)
{
	this.state.dashPattern = value;
};

/**
 * Function: setLineCap
 * 
 * Sets the current line cap.
 */
mxAbstractCanvas2D.prototype.setLineCap = function(value)
{
	this.state.lineCap = value;
};

/**
 * Function: setLineJoin
 * 
 * Sets the current line join.
 */
mxAbstractCanvas2D.prototype.setLineJoin = function(value)
{
	this.state.lineJoin = value;
};

/**
 * Function: setMiterLimit
 * 
 * Sets the current miter limit.
 */
mxAbstractCanvas2D.prototype.setMiterLimit = function(value)
{
	this.state.miterLimit = value;
};

/**
 * Function: setFontColor
 * 
 * Sets the current font color.
 */
mxAbstractCanvas2D.prototype.setFontColor = function(value)
{
	if (value == mxConstants.NONE)
	{
		value = null;
	}
	
	this.state.fontColor = value;
};

/**
 * Function: setFontColor
 * 
 * Sets the current font color.
 */
mxAbstractCanvas2D.prototype.setFontBackgroundColor = function(value)
{
	if (value == mxConstants.NONE)
	{
		value = null;
	}
	
	this.state.fontBackgroundColor = value;
};

/**
 * Function: setFontColor
 * 
 * Sets the current font color.
 */
mxAbstractCanvas2D.prototype.setFontBorderColor = function(value)
{
	if (value == mxConstants.NONE)
	{
		value = null;
	}
	
	this.state.fontBorderColor = value;
};

/**
 * Function: setFontSize
 * 
 * Sets the current font size.
 */
mxAbstractCanvas2D.prototype.setFontSize = function(value)
{
	this.state.fontSize = parseFloat(value);
};

/**
 * Function: setFontFamily
 * 
 * Sets the current font family.
 */
mxAbstractCanvas2D.prototype.setFontFamily = function(value)
{
	this.state.fontFamily = value;
};

/**
 * Function: setFontStyle
 * 
 * Sets the current font style.
 */
mxAbstractCanvas2D.prototype.setFontStyle = function(value)
{
	if (value == null)
	{
		value = 0;
	}
	
	this.state.fontStyle = value;
};

/**
 * Function: setShadow
 * 
 * Enables or disables and configures the current shadow.
 */
mxAbstractCanvas2D.prototype.setShadow = function(enabled)
{
	this.state.shadow = enabled;
};

/**
 * Function: setShadowColor
 * 
 * Enables or disables and configures the current shadow.
 */
mxAbstractCanvas2D.prototype.setShadowColor = function(value)
{
	if (value == mxConstants.NONE)
	{
		value = null;
	}
	
	this.state.shadowColor = value;
};

/**
 * Function: setShadowAlpha
 * 
 * Enables or disables and configures the current shadow.
 */
mxAbstractCanvas2D.prototype.setShadowAlpha = function(value)
{
	this.state.shadowAlpha = value;
};

/**
 * Function: setShadowOffset
 * 
 * Enables or disables and configures the current shadow.
 */
mxAbstractCanvas2D.prototype.setShadowOffset = function(dx, dy)
{
	this.state.shadowDx = dx;
	this.state.shadowDy = dy;
};

/**
 * Function: begin
 * 
 * Starts a new path.
 */
mxAbstractCanvas2D.prototype.begin = function()
{
	this.lastX = 0;
	this.lastY = 0;
	this.path = [];
};

/**
 * Function: moveTo
 * 
 *  Moves the current path the given coordinates.
 */
mxAbstractCanvas2D.prototype.moveTo = function(x, y)
{
	this.addOp(this.moveOp, x, y);
};

/**
 * Function: lineTo
 * 
 * Draws a line to the given coordinates. Uses moveTo with the op argument.
 */
mxAbstractCanvas2D.prototype.lineTo = function(x, y)
{
	this.addOp(this.lineOp, x, y);
};

/**
 * Function: quadTo
 * 
 * Adds a quadratic curve to the current path.
 */
mxAbstractCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
{
	this.addOp(this.quadOp, x1, y1, x2, y2);
};

/**
 * Function: curveTo
 * 
 * Adds a bezier curve to the current path.
 */
mxAbstractCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
{
	this.addOp(this.curveOp, x1, y1, x2, y2, x3, y3);
};

/**
 * Function: arcTo
 * 
 * Adds the given arc to the current path. This is a synthetic operation that
 * is broken down into curves.
 */
mxAbstractCanvas2D.prototype.arcTo = function(rx, ry, angle, largeArcFlag, sweepFlag, x, y)
{
	var curves = mxUtils.arcToCurves(this.lastX, this.lastY, rx, ry, angle, largeArcFlag, sweepFlag, x, y);
	
	if (curves != null)
	{
		for (var i = 0; i < curves.length; i += 6) 
		{
			this.curveTo(curves[i], curves[i + 1], curves[i + 2],
				curves[i + 3], curves[i + 4], curves[i + 5]);
		}
	}
};

/**
 * Function: close
 * 
 * Closes the current path.
 */
mxAbstractCanvas2D.prototype.close = function(x1, y1, x2, y2, x3, y3)
{
	this.addOp(this.closeOp);
};

/**
 * Function: end
 * 
 * Empty implementation for backwards compatibility. This will be removed.
 */
mxAbstractCanvas2D.prototype.end = function() { };
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxXmlCanvas2D
 *
 * Base class for all canvases. The following methods make up the public
 * interface of the canvas 2D for all painting in mxGraph:
 * 
 * - , 
 * - , , 
 * - , , , , ,
 *   , , , , , 
 *   , 
 * - , , , ,
 *   , 
 * - , , , 
 * - , , , , 
 * - , , , , 
 * - , , 
 * 
 *  is an additional method for drawing paths. This is
 * a synthetic method, meaning that it is turned into a sequence of curves by
 * default. Subclassers may add native support for arcs.
 * 
 * Constructor: mxXmlCanvas2D
 *
 * Constructs a new abstract canvas.
 */
function mxXmlCanvas2D(root)
{
	mxAbstractCanvas2D.call(this);

	/**
	 * Variable: root
	 * 
	 * Reference to the container for the SVG content.
	 */
	this.root = root;

	// Writes default settings;
	this.writeDefaults();
};

/**
 * Extends mxAbstractCanvas2D
 */
mxUtils.extend(mxXmlCanvas2D, mxAbstractCanvas2D);

/**
 * Variable: textEnabled
 * 
 * Specifies if text output should be enabled. Default is true.
 */
mxXmlCanvas2D.prototype.textEnabled = true;

/**
 * Variable: compressed
 * 
 * Specifies if the output should be compressed by removing redundant calls.
 * Default is true.
 */
mxXmlCanvas2D.prototype.compressed = true;

/**
 * Function: writeDefaults
 * 
 * Writes the rendering defaults to :
 */
mxXmlCanvas2D.prototype.writeDefaults = function()
{
	var elem;
	
	// Writes font defaults
	elem = this.createElement('fontfamily');
	elem.setAttribute('family', mxConstants.DEFAULT_FONTFAMILY);
	this.root.appendChild(elem);
	
	elem = this.createElement('fontsize');
	elem.setAttribute('size', mxConstants.DEFAULT_FONTSIZE);
	this.root.appendChild(elem);
	
	// Writes shadow defaults
	elem = this.createElement('shadowcolor');
	elem.setAttribute('color', mxConstants.SHADOWCOLOR);
	this.root.appendChild(elem);
	
	elem = this.createElement('shadowalpha');
	elem.setAttribute('alpha', mxConstants.SHADOW_OPACITY);
	this.root.appendChild(elem);
	
	elem = this.createElement('shadowoffset');
	elem.setAttribute('dx', mxConstants.SHADOW_OFFSET_X);
	elem.setAttribute('dy', mxConstants.SHADOW_OFFSET_Y);
	this.root.appendChild(elem);
};

/**
 * Function: format
 * 
 * Returns a formatted number with 2 decimal places.
 */
mxXmlCanvas2D.prototype.format = function(value)
{
	return parseFloat(parseFloat(value).toFixed(2));
};

/**
 * Function: createElement
 * 
 * Creates the given element using the owner document of .
 */
mxXmlCanvas2D.prototype.createElement = function(name)
{
	return this.root.ownerDocument.createElement(name);
};

/**
 * Function: save
 * 
 * Saves the drawing state.
 */
mxXmlCanvas2D.prototype.save = function()
{
	if (this.compressed)
	{
		mxAbstractCanvas2D.prototype.save.apply(this, arguments);
	}
	
	this.root.appendChild(this.createElement('save'));
};

/**
 * Function: restore
 * 
 * Restores the drawing state.
 */
mxXmlCanvas2D.prototype.restore = function()
{
	if (this.compressed)
	{
		mxAbstractCanvas2D.prototype.restore.apply(this, arguments);
	}
	
	this.root.appendChild(this.createElement('restore'));
};

/**
 * Function: scale
 * 
 * Scales the output.
 * 
 * Parameters:
 * 
 * scale - Number that represents the scale where 1 is equal to 100%.
 */
mxXmlCanvas2D.prototype.scale = function(value)
{
        var elem = this.createElement('scale');
        elem.setAttribute('scale', value);
        this.root.appendChild(elem);
};

/**
 * Function: translate
 * 
 * Translates the output.
 * 
 * Parameters:
 * 
 * dx - Number that specifies the horizontal translation.
 * dy - Number that specifies the vertical translation.
 */
mxXmlCanvas2D.prototype.translate = function(dx, dy)
{
	var elem = this.createElement('translate');
	elem.setAttribute('dx', this.format(dx));
	elem.setAttribute('dy', this.format(dy));
	this.root.appendChild(elem);
};

/**
 * Function: rotate
 * 
 * Rotates and/or flips the output around a given center. (Note: Due to
 * limitations in VML, the rotation cannot be concatenated.)
 * 
 * Parameters:
 * 
 * theta - Number that represents the angle of the rotation (in degrees).
 * flipH - Boolean indicating if the output should be flipped horizontally.
 * flipV - Boolean indicating if the output should be flipped vertically.
 * cx - Number that represents the x-coordinate of the rotation center.
 * cy - Number that represents the y-coordinate of the rotation center.
 */
mxXmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
{
	var elem = this.createElement('rotate');
	
	if (theta != 0 || flipH || flipV)
	{
		elem.setAttribute('theta', this.format(theta));
		elem.setAttribute('flipH', (flipH) ? '1' : '0');
		elem.setAttribute('flipV', (flipV) ? '1' : '0');
		elem.setAttribute('cx', this.format(cx));
		elem.setAttribute('cy', this.format(cy));
		this.root.appendChild(elem);
	}
};

/**
 * Function: setAlpha
 * 
 * Sets the current alpha.
 * 
 * Parameters:
 * 
 * value - Number that represents the new alpha. Possible values are between
 * 1 (opaque) and 0 (transparent).
 */
mxXmlCanvas2D.prototype.setAlpha = function(value)
{
	if (this.compressed)
	{
		if (this.state.alpha == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setAlpha.apply(this, arguments);
	}
	
	var elem = this.createElement('alpha');
	elem.setAttribute('alpha', this.format(value));
	this.root.appendChild(elem);
};

/**
 * Function: setFillAlpha
 * 
 * Sets the current fill alpha.
 * 
 * Parameters:
 * 
 * value - Number that represents the new fill alpha. Possible values are between
 * 1 (opaque) and 0 (transparent).
 */
mxXmlCanvas2D.prototype.setFillAlpha = function(value)
{
	if (this.compressed)
	{
		if (this.state.fillAlpha == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setFillAlpha.apply(this, arguments);
	}
	
	var elem = this.createElement('fillalpha');
	elem.setAttribute('alpha', this.format(value));
	this.root.appendChild(elem);
};

/**
 * Function: setStrokeAlpha
 * 
 * Sets the current stroke alpha.
 * 
 * Parameters:
 * 
 * value - Number that represents the new stroke alpha. Possible values are between
 * 1 (opaque) and 0 (transparent).
 */
mxXmlCanvas2D.prototype.setStrokeAlpha = function(value)
{
	if (this.compressed)
	{
		if (this.state.strokeAlpha == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setStrokeAlpha.apply(this, arguments);
	}
	
	var elem = this.createElement('strokealpha');
	elem.setAttribute('alpha', this.format(value));
	this.root.appendChild(elem);
};

/**
 * Function: setFillColor
 * 
 * Sets the current fill color.
 * 
 * Parameters:
 * 
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setFillColor = function(value)
{
	if (value == mxConstants.NONE)
	{
		value = null;
	}
	
	if (this.compressed)
	{
		if (this.state.fillColor == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setFillColor.apply(this, arguments);
	}
	
	var elem = this.createElement('fillcolor');
	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
	this.root.appendChild(elem);
};

/**
 * Function: setGradient
 * 
 * Sets the gradient. Note that the coordinates may be ignored by some implementations.
 * 
 * Parameters:
 * 
 * color1 - Hexadecimal representation of the start color.
 * color2 - Hexadecimal representation of the end color.
 * x - X-coordinate of the gradient region.
 * y - y-coordinate of the gradient region.
 * w - Width of the gradient region.
 * h - Height of the gradient region.
 * direction - One of , ,
 *  or .
 * alpha1 - Optional alpha of the start color. Default is 1. Possible values
 * are between 1 (opaque) and 0 (transparent).
 * alpha2 - Optional alpha of the end color. Default is 1. Possible values
 * are between 1 (opaque) and 0 (transparent).
 */
mxXmlCanvas2D.prototype.setGradient = function(color1, color2, x, y, w, h, direction, alpha1, alpha2)
{
	if (color1 != null && color2 != null)
	{
		mxAbstractCanvas2D.prototype.setGradient.apply(this, arguments);
		
		var elem = this.createElement('gradient');
		elem.setAttribute('c1', color1);
		elem.setAttribute('c2', color2);
		elem.setAttribute('x', this.format(x));
		elem.setAttribute('y', this.format(y));
		elem.setAttribute('w', this.format(w));
		elem.setAttribute('h', this.format(h));
		
		// Default direction is south
		if (direction != null)
		{
			elem.setAttribute('direction', direction);
		}
		
		if (alpha1 != null)
		{
			elem.setAttribute('alpha1', alpha1);
		}
		
		if (alpha2 != null)
		{
			elem.setAttribute('alpha2', alpha2);
		}
		
		this.root.appendChild(elem);
	}
};

/**
 * Function: setStrokeColor
 * 
 * Sets the current stroke color.
 * 
 * Parameters:
 * 
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setStrokeColor = function(value)
{
	if (value == mxConstants.NONE)
	{
		value = null;
	}
	
	if (this.compressed)
	{
		if (this.state.strokeColor == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setStrokeColor.apply(this, arguments);
	}
	
	var elem = this.createElement('strokecolor');
	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
	this.root.appendChild(elem);
};

/**
 * Function: setStrokeWidth
 * 
 * Sets the current stroke width.
 * 
 * Parameters:
 * 
 * value - Numeric representation of the stroke width.
 */
mxXmlCanvas2D.prototype.setStrokeWidth = function(value)
{
	if (this.compressed)
	{
		if (this.state.strokeWidth == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setStrokeWidth.apply(this, arguments);
	}
	
	var elem = this.createElement('strokewidth');
	elem.setAttribute('width', this.format(value));
	this.root.appendChild(elem);
};

/**
 * Function: setDashed
 * 
 * Enables or disables dashed lines.
 * 
 * Parameters:
 * 
 * value - Boolean that specifies if dashed lines should be enabled.
 * value - Boolean that specifies if the stroke width should be ignored
 * for the dash pattern. Default is false.
 */
mxXmlCanvas2D.prototype.setDashed = function(value, fixDash)
{
	if (this.compressed)
	{
		if (this.state.dashed == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setDashed.apply(this, arguments);
	}
	
	var elem = this.createElement('dashed');
	elem.setAttribute('dashed', (value) ? '1' : '0');
	
	if (fixDash != null)
	{
		elem.setAttribute('fixDash', (fixDash) ? '1' : '0');
	}
	
	this.root.appendChild(elem);
};

/**
 * Function: setDashPattern
 * 
 * Sets the current dash pattern. Default is '3 3'.
 * 
 * Parameters:
 * 
 * value - String that represents the dash pattern, which is a sequence of
 * numbers defining the length of the dashes and the length of the spaces
 * between the dashes. The lengths are relative to the line width - a length
 * of 1 is equals to the line width.
 */
mxXmlCanvas2D.prototype.setDashPattern = function(value)
{
	if (this.compressed)
	{
		if (this.state.dashPattern == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setDashPattern.apply(this, arguments);
	}
	
	var elem = this.createElement('dashpattern');
	elem.setAttribute('pattern', value);
	this.root.appendChild(elem);
};

/**
 * Function: setLineCap
 * 
 * Sets the line cap. Default is 'flat' which corresponds to 'butt' in SVG.
 * 
 * Parameters:
 * 
 * value - String that represents the line cap. Possible values are flat, round
 * and square.
 */
mxXmlCanvas2D.prototype.setLineCap = function(value)
{
	if (this.compressed)
	{
		if (this.state.lineCap == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setLineCap.apply(this, arguments);
	}
	
	var elem = this.createElement('linecap');
	elem.setAttribute('cap', value);
	this.root.appendChild(elem);
};

/**
 * Function: setLineJoin
 * 
 * Sets the line join. Default is 'miter'.
 * 
 * Parameters:
 * 
 * value - String that represents the line join. Possible values are miter,
 * round and bevel.
 */
mxXmlCanvas2D.prototype.setLineJoin = function(value)
{
	if (this.compressed)
	{
		if (this.state.lineJoin == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setLineJoin.apply(this, arguments);
	}
	
	var elem = this.createElement('linejoin');
	elem.setAttribute('join', value);
	this.root.appendChild(elem);
};

/**
 * Function: setMiterLimit
 * 
 * Sets the miter limit. Default is 10.
 * 
 * Parameters:
 * 
 * value - Number that represents the miter limit.
 */
mxXmlCanvas2D.prototype.setMiterLimit = function(value)
{
	if (this.compressed)
	{
		if (this.state.miterLimit == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setMiterLimit.apply(this, arguments);
	}
	
	var elem = this.createElement('miterlimit');
	elem.setAttribute('limit', value);
	this.root.appendChild(elem);
};

/**
 * Function: setFontColor
 * 
 * Sets the current font color. Default is '#000000'.
 * 
 * Parameters:
 * 
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setFontColor = function(value)
{
	if (this.textEnabled)
	{
		if (value == mxConstants.NONE)
		{
			value = null;
		}
		
		if (this.compressed)
		{
			if (this.state.fontColor == value)
			{
				return;
			}
			
			mxAbstractCanvas2D.prototype.setFontColor.apply(this, arguments);
		}
		
		var elem = this.createElement('fontcolor');
		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
		this.root.appendChild(elem);
	}
};

/**
 * Function: setFontBackgroundColor
 * 
 * Sets the current font background color.
 * 
 * Parameters:
 * 
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setFontBackgroundColor = function(value)
{
	if (this.textEnabled)
	{
		if (value == mxConstants.NONE)
		{
			value = null;
		}
		
		if (this.compressed)
		{
			if (this.state.fontBackgroundColor == value)
			{
				return;
			}
			
			mxAbstractCanvas2D.prototype.setFontBackgroundColor.apply(this, arguments);
		}

		var elem = this.createElement('fontbackgroundcolor');
		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
		this.root.appendChild(elem);
	}
};

/**
 * Function: setFontBorderColor
 * 
 * Sets the current font border color.
 * 
 * Parameters:
 * 
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setFontBorderColor = function(value)
{
	if (this.textEnabled)
	{
		if (value == mxConstants.NONE)
		{
			value = null;
		}
		
		if (this.compressed)
		{
			if (this.state.fontBorderColor == value)
			{
				return;
			}
			
			mxAbstractCanvas2D.prototype.setFontBorderColor.apply(this, arguments);
		}
		
		var elem = this.createElement('fontbordercolor');
		elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
		this.root.appendChild(elem);
	}
};

/**
 * Function: setFontSize
 * 
 * Sets the current font size. Default is .
 * 
 * Parameters:
 * 
 * value - Numeric representation of the font size.
 */
mxXmlCanvas2D.prototype.setFontSize = function(value)
{
	if (this.textEnabled)
	{
		if (this.compressed)
		{
			if (this.state.fontSize == value)
			{
				return;
			}
			
			mxAbstractCanvas2D.prototype.setFontSize.apply(this, arguments);
		}
		
		var elem = this.createElement('fontsize');
		elem.setAttribute('size', value);
		this.root.appendChild(elem);
	}
};

/**
 * Function: setFontFamily
 * 
 * Sets the current font family. Default is .
 * 
 * Parameters:
 * 
 * value - String representation of the font family. This handles the same
 * values as the CSS font-family property.
 */
mxXmlCanvas2D.prototype.setFontFamily = function(value)
{
	if (this.textEnabled)
	{
		if (this.compressed)
		{
			if (this.state.fontFamily == value)
			{
				return;
			}
			
			mxAbstractCanvas2D.prototype.setFontFamily.apply(this, arguments);
		}
		
		var elem = this.createElement('fontfamily');
		elem.setAttribute('family', value);
		this.root.appendChild(elem);
	}
};

/**
 * Function: setFontStyle
 * 
 * Sets the current font style.
 * 
 * Parameters:
 * 
 * value - Numeric representation of the font family. This is the sum of the
 * font styles from .
 */
mxXmlCanvas2D.prototype.setFontStyle = function(value)
{
	if (this.textEnabled)
	{
		if (value == null)
		{
			value = 0;
		}
		
		if (this.compressed)
		{
			if (this.state.fontStyle == value)
			{
				return;
			}
			
			mxAbstractCanvas2D.prototype.setFontStyle.apply(this, arguments);
		}
		
		var elem = this.createElement('fontstyle');
		elem.setAttribute('style', value);
		this.root.appendChild(elem);
	}
};

/**
 * Function: setShadow
 * 
 * Enables or disables shadows.
 * 
 * Parameters:
 * 
 * value - Boolean that specifies if shadows should be enabled.
 */
mxXmlCanvas2D.prototype.setShadow = function(value)
{
	if (this.compressed)
	{
		if (this.state.shadow == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setShadow.apply(this, arguments);
	}
	
	var elem = this.createElement('shadow');
	elem.setAttribute('enabled', (value) ? '1' : '0');
	this.root.appendChild(elem);
};

/**
 * Function: setShadowColor
 * 
 * Sets the current shadow color. Default is .
 * 
 * Parameters:
 * 
 * value - Hexadecimal representation of the color or 'none'.
 */
mxXmlCanvas2D.prototype.setShadowColor = function(value)
{
	if (this.compressed)
	{
		if (value == mxConstants.NONE)
		{
			value = null;
		}
		
		if (this.state.shadowColor == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setShadowColor.apply(this, arguments);
	}
	
	var elem = this.createElement('shadowcolor');
	elem.setAttribute('color', (value != null) ? value : mxConstants.NONE);
	this.root.appendChild(elem);
};

/**
 * Function: setShadowAlpha
 * 
 * Sets the current shadows alpha. Default is .
 * 
 * Parameters:
 * 
 * value - Number that represents the new alpha. Possible values are between
 * 1 (opaque) and 0 (transparent).
 */
mxXmlCanvas2D.prototype.setShadowAlpha = function(value)
{
	if (this.compressed)
	{
		if (this.state.shadowAlpha == value)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setShadowAlpha.apply(this, arguments);
	}
	
	var elem = this.createElement('shadowalpha');
	elem.setAttribute('alpha', value);
	this.root.appendChild(elem);
	
};

/**
 * Function: setShadowOffset
 * 
 * Sets the current shadow offset.
 * 
 * Parameters:
 * 
 * dx - Number that represents the horizontal offset of the shadow.
 * dy - Number that represents the vertical offset of the shadow.
 */
mxXmlCanvas2D.prototype.setShadowOffset = function(dx, dy)
{
	if (this.compressed)
	{
		if (this.state.shadowDx == dx && this.state.shadowDy == dy)
		{
			return;
		}
		
		mxAbstractCanvas2D.prototype.setShadowOffset.apply(this, arguments);
	}
	
	var elem = this.createElement('shadowoffset');
	elem.setAttribute('dx', dx);
	elem.setAttribute('dy', dy);
	this.root.appendChild(elem);
	
};

/**
 * Function: rect
 * 
 * Puts a rectangle into the drawing buffer.
 * 
 * Parameters:
 * 
 * x - Number that represents the x-coordinate of the rectangle.
 * y - Number that represents the y-coordinate of the rectangle.
 * w - Number that represents the width of the rectangle.
 * h - Number that represents the height of the rectangle.
 */
mxXmlCanvas2D.prototype.rect = function(x, y, w, h)
{
	var elem = this.createElement('rect');
	elem.setAttribute('x', this.format(x));
	elem.setAttribute('y', this.format(y));
	elem.setAttribute('w', this.format(w));
	elem.setAttribute('h', this.format(h));
	this.root.appendChild(elem);
};

/**
 * Function: roundrect
 * 
 * Puts a rounded rectangle into the drawing buffer.
 * 
 * Parameters:
 * 
 * x - Number that represents the x-coordinate of the rectangle.
 * y - Number that represents the y-coordinate of the rectangle.
 * w - Number that represents the width of the rectangle.
 * h - Number that represents the height of the rectangle.
 * dx - Number that represents the horizontal rounding.
 * dy - Number that represents the vertical rounding.
 */
mxXmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
{
	var elem = this.createElement('roundrect');
	elem.setAttribute('x', this.format(x));
	elem.setAttribute('y', this.format(y));
	elem.setAttribute('w', this.format(w));
	elem.setAttribute('h', this.format(h));
	elem.setAttribute('dx', this.format(dx));
	elem.setAttribute('dy', this.format(dy));
	this.root.appendChild(elem);
};

/**
 * Function: ellipse
 * 
 * Puts an ellipse into the drawing buffer.
 * 
 * Parameters:
 * 
 * x - Number that represents the x-coordinate of the ellipse.
 * y - Number that represents the y-coordinate of the ellipse.
 * w - Number that represents the width of the ellipse.
 * h - Number that represents the height of the ellipse.
 */
mxXmlCanvas2D.prototype.ellipse = function(x, y, w, h)
{
	var elem = this.createElement('ellipse');
	elem.setAttribute('x', this.format(x));
	elem.setAttribute('y', this.format(y));
	elem.setAttribute('w', this.format(w));
	elem.setAttribute('h', this.format(h));
	this.root.appendChild(elem);
};

/**
 * Function: image
 * 
 * Paints an image.
 * 
 * Parameters:
 * 
 * x - Number that represents the x-coordinate of the image.
 * y - Number that represents the y-coordinate of the image.
 * w - Number that represents the width of the image.
 * h - Number that represents the height of the image.
 * src - String that specifies the URL of the image.
 * aspect - Boolean indicating if the aspect of the image should be preserved.
 * flipH - Boolean indicating if the image should be flipped horizontally.
 * flipV - Boolean indicating if the image should be flipped vertically.
 */
mxXmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
{
	src = this.converter.convert(src);
	
	// LATER: Add option for embedding images as base64.
	var elem = this.createElement('image');
	elem.setAttribute('x', this.format(x));
	elem.setAttribute('y', this.format(y));
	elem.setAttribute('w', this.format(w));
	elem.setAttribute('h', this.format(h));
	elem.setAttribute('src', src);
	elem.setAttribute('aspect', (aspect) ? '1' : '0');
	elem.setAttribute('flipH', (flipH) ? '1' : '0');
	elem.setAttribute('flipV', (flipV) ? '1' : '0');
	this.root.appendChild(elem);
};

/**
 * Function: begin
 * 
 * Starts a new path and puts it into the drawing buffer.
 */
mxXmlCanvas2D.prototype.begin = function()
{
	this.root.appendChild(this.createElement('begin'));
	this.lastX = 0;
	this.lastY = 0;
};

/**
 * Function: moveTo
 * 
 * Moves the current path the given point.
 * 
 * Parameters:
 * 
 * x - Number that represents the x-coordinate of the point.
 * y - Number that represents the y-coordinate of the point.
 */
mxXmlCanvas2D.prototype.moveTo = function(x, y)
{
	var elem = this.createElement('move');
	elem.setAttribute('x', this.format(x));
	elem.setAttribute('y', this.format(y));
	this.root.appendChild(elem);
	this.lastX = x;
	this.lastY = y;
};

/**
 * Function: lineTo
 * 
 * Draws a line to the given coordinates.
 * 
 * Parameters:
 * 
 * x - Number that represents the x-coordinate of the endpoint.
 * y - Number that represents the y-coordinate of the endpoint.
 */
mxXmlCanvas2D.prototype.lineTo = function(x, y)
{
	var elem = this.createElement('line');
	elem.setAttribute('x', this.format(x));
	elem.setAttribute('y', this.format(y));
	this.root.appendChild(elem);
	this.lastX = x;
	this.lastY = y;
};

/**
 * Function: quadTo
 * 
 * Adds a quadratic curve to the current path.
 * 
 * Parameters:
 * 
 * x1 - Number that represents the x-coordinate of the control point.
 * y1 - Number that represents the y-coordinate of the control point.
 * x2 - Number that represents the x-coordinate of the endpoint.
 * y2 - Number that represents the y-coordinate of the endpoint.
 */
mxXmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2)
{
	var elem = this.createElement('quad');
	elem.setAttribute('x1', this.format(x1));
	elem.setAttribute('y1', this.format(y1));
	elem.setAttribute('x2', this.format(x2));
	elem.setAttribute('y2', this.format(y2));
	this.root.appendChild(elem);
	this.lastX = x2;
	this.lastY = y2;
};

/**
 * Function: curveTo
 * 
 * Adds a bezier curve to the current path.
 * 
 * Parameters:
 * 
 * x1 - Number that represents the x-coordinate of the first control point.
 * y1 - Number that represents the y-coordinate of the first control point.
 * x2 - Number that represents the x-coordinate of the second control point.
 * y2 - Number that represents the y-coordinate of the second control point.
 * x3 - Number that represents the x-coordinate of the endpoint.
 * y3 - Number that represents the y-coordinate of the endpoint.
 */
mxXmlCanvas2D.prototype.curveTo = function(x1, y1, x2, y2, x3, y3)
{
	var elem = this.createElement('curve');
	elem.setAttribute('x1', this.format(x1));
	elem.setAttribute('y1', this.format(y1));
	elem.setAttribute('x2', this.format(x2));
	elem.setAttribute('y2', this.format(y2));
	elem.setAttribute('x3', this.format(x3));
	elem.setAttribute('y3', this.format(y3));
	this.root.appendChild(elem);
	this.lastX = x3;
	this.lastY = y3;
};

/**
 * Function: close
 * 
 * Closes the current path.
 */
mxXmlCanvas2D.prototype.close = function()
{
	this.root.appendChild(this.createElement('close'));
};

/**
 * Function: text
 * 
 * Paints the given text. Possible values for format are empty string for
 * plain text and html for HTML markup. Background and border color as well
 * as clipping is not available in plain text labels for VML. HTML labels
 * are not available as part of shapes with no foreignObject support in SVG
 * (eg. IE9, IE10).
 * 
 * Parameters:
 * 
 * x - Number that represents the x-coordinate of the text.
 * y - Number that represents the y-coordinate of the text.
 * w - Number that represents the available width for the text or 0 for automatic width.
 * h - Number that represents the available height for the text or 0 for automatic height.
 * str - String that specifies the text to be painted.
 * align - String that represents the horizontal alignment.
 * valign - String that represents the vertical alignment.
 * wrap - Boolean that specifies if word-wrapping is enabled. Requires w > 0.
 * format - Empty string for plain text or 'html' for HTML markup.
 * overflow - Specifies the overflow behaviour of the label. Requires w > 0 and/or h > 0.
 * clip - Boolean that specifies if the label should be clipped. Requires w > 0 and/or h > 0.
 * rotation - Number that specifies the angle of the rotation around the anchor point of the text.
 * dir - Optional string that specifies the text direction. Possible values are rtl and lrt.
 */
mxXmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir)
{
	if (this.textEnabled && str != null)
	{
		if (mxUtils.isNode(str))
		{
			str = mxUtils.getOuterHtml(str);
		}
		
		var elem = this.createElement('text');
		elem.setAttribute('x', this.format(x));
		elem.setAttribute('y', this.format(y));
		elem.setAttribute('w', this.format(w));
		elem.setAttribute('h', this.format(h));
		elem.setAttribute('str', str);
		
		if (align != null)
		{
			elem.setAttribute('align', align);
		}
		
		if (valign != null)
		{
			elem.setAttribute('valign', valign);
		}
		
		elem.setAttribute('wrap', (wrap) ? '1' : '0');
		
		if (format == null)
		{
			format = '';
		}
		
		elem.setAttribute('format', format);
		
		if (overflow != null)
		{
			elem.setAttribute('overflow', overflow);
		}
		
		if (clip != null)
		{
			elem.setAttribute('clip', (clip) ? '1' : '0');
		}
		
		if (rotation != null)
		{
			elem.setAttribute('rotation', rotation);
		}
		
		if (dir != null)
		{
			elem.setAttribute('dir', dir);
		}
		
		this.root.appendChild(elem);
	}
};

/**
 * Function: stroke
 * 
 * Paints the outline of the current drawing buffer.
 */
mxXmlCanvas2D.prototype.stroke = function()
{
	this.root.appendChild(this.createElement('stroke'));
};

/**
 * Function: fill
 * 
 * Fills the current drawing buffer.
 */
mxXmlCanvas2D.prototype.fill = function()
{
	this.root.appendChild(this.createElement('fill'));
};

/**
 * Function: fillAndStroke
 * 
 * Fills the current drawing buffer and its outline.
 */
mxXmlCanvas2D.prototype.fillAndStroke = function()
{
	this.root.appendChild(this.createElement('fillstroke'));
};
/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxSvgCanvas2D
 *
 * Extends  to implement a canvas for SVG. This canvas writes all
 * calls as SVG output to the given SVG root node.
 * 
 * (code)
 * var svgDoc = mxUtils.createXmlDocument();
 * var root = (svgDoc.createElementNS != null) ?
 * 		svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg');
 * 
 * if (svgDoc.createElementNS == null)
 * {
 *   root.setAttribute('xmlns', mxConstants.NS_SVG);
 *   root.setAttribute('xmlns:xlink', mxConstants.NS_XLINK);
 * }
 * else
 * {
 *   root.setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xlink', mxConstants.NS_XLINK);
 * }
 * 
 * var bounds = graph.getGraphBounds();
 * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px');
 * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px');
 * root.setAttribute('version', '1.1');
 * 
 * svgDoc.appendChild(root);
 * 
 * var svgCanvas = new mxSvgCanvas2D(root);
 * (end)
 * 
 * A description of the public API is available in .
 * 
 * To disable anti-aliasing in the output, use the following code.
 * 
 * (code)
 * graph.view.canvas.ownerSVGElement.setAttribute('shape-rendering', 'crispEdges');
 * (end)
 * 
 * Or set the respective attribute in the SVG element directly.
 * 
 * Constructor: mxSvgCanvas2D
 *
 * Constructs a new SVG canvas.
 * 
 * Parameters:
 * 
 * root - SVG container for the output.
 * styleEnabled - Optional boolean that specifies if a style section should be
 * added. The style section sets the default font-size, font-family and
 * stroke-miterlimit globally. Default is false.
 */
function mxSvgCanvas2D(root, styleEnabled)
{
	mxAbstractCanvas2D.call(this);

	/**
	 * Variable: root
	 * 
	 * Reference to the container for the SVG content.
	 */
	this.root = root;

	/**
	 * Variable: gradients
	 * 
	 * Local cache of gradients for quick lookups.
	 */
	this.gradients = [];

	/**
	 * Variable: defs
	 * 
	 * Reference to the defs section of the SVG document. Only for export.
	 */
	this.defs = null;
	
	/**
	 * Variable: styleEnabled
	 * 
	 * Stores the value of styleEnabled passed to the constructor.
	 */
	this.styleEnabled = (styleEnabled != null) ? styleEnabled : false;
	
	var svg = null;
	
	// Adds optional defs section for export
	if (root.ownerDocument != document)
	{
		var node = root;

		// Finds owner SVG element in XML DOM
		while (node != null && node.nodeName != 'svg')
		{
			node = node.parentNode;
		}
		
		svg = node;
	}

	if (svg != null)
	{
		// Tries to get existing defs section
		var tmp = svg.getElementsByTagName('defs');
		
		if (tmp.length > 0)
		{
			this.defs = svg.getElementsByTagName('defs')[0];
		}
		
		// Adds defs section if none exists
		if (this.defs == null)
		{
			this.defs = this.createElement('defs');
			
			if (svg.firstChild != null)
			{
				svg.insertBefore(this.defs, svg.firstChild);
			}
			else
			{
				svg.appendChild(this.defs);
			}
		}

		// Adds stylesheet
		if (this.styleEnabled)
		{
			this.defs.appendChild(this.createStyle());
		}
	}
};

/**
 * Extends mxAbstractCanvas2D
 */
mxUtils.extend(mxSvgCanvas2D, mxAbstractCanvas2D);

/**
 * Capability check for DOM parser.
 */
(function()
{
	mxSvgCanvas2D.prototype.useDomParser = !mxClient.IS_IE && typeof DOMParser === 'function' && typeof XMLSerializer === 'function';
	
	if (mxSvgCanvas2D.prototype.useDomParser)
	{
		// Checks using a generic test text if the parsing actually works. This is a workaround
		// for older browsers where the capability check returns true but the parsing fails.
		try
		{
			var doc = new DOMParser().parseFromString('test text', 'text/html');
			mxSvgCanvas2D.prototype.useDomParser = doc != null;
		}
		catch (e)
		{
			mxSvgCanvas2D.prototype.useDomParser = false;
		}
	}
})();

/**
 * Variable: path
 * 
 * Holds the current DOM node.
 */
mxSvgCanvas2D.prototype.node = null;

/**
 * Variable: matchHtmlAlignment
 * 
 * Specifies if plain text output should match the vertical HTML alignment.
 * Defaul is true.
 */
mxSvgCanvas2D.prototype.matchHtmlAlignment = true;

/**
 * Variable: textEnabled
 * 
 * Specifies if text output should be enabled. Default is true.
 */
mxSvgCanvas2D.prototype.textEnabled = true;

/**
 * Variable: foEnabled
 * 
 * Specifies if use of foreignObject for HTML markup is allowed. Default is true.
 */
mxSvgCanvas2D.prototype.foEnabled = true;

/**
 * Variable: foAltText
 * 
 * Specifies the fallback text for unsupported foreignObjects in exported
 * documents. Default is '[Object]'. If this is set to null then no fallback
 * text is added to the exported document.
 */
mxSvgCanvas2D.prototype.foAltText = '[Object]';

/**
 * Variable: foOffset
 * 
 * Offset to be used for foreignObjects.
 */
mxSvgCanvas2D.prototype.foOffset = 0;

/**
 * Variable: textOffset
 * 
 * Offset to be used for text elements.
 */
mxSvgCanvas2D.prototype.textOffset = 0;

/**
 * Variable: imageOffset
 * 
 * Offset to be used for image elements.
 */
mxSvgCanvas2D.prototype.imageOffset = 0;

/**
 * Variable: strokeTolerance
 * 
 * Adds transparent paths for strokes.
 */
mxSvgCanvas2D.prototype.strokeTolerance = 0;

/**
 * Variable: minStrokeWidth
 * 
 * Minimum stroke width for output.
 */
mxSvgCanvas2D.prototype.minStrokeWidth = 1;

/**
 * Variable: refCount
 * 
 * Local counter for references in SVG export.
 */
mxSvgCanvas2D.prototype.refCount = 0;

/**
 * Variable: lineHeightCorrection
 * 
 * Correction factor for  in HTML output. Default is 1.
 */
mxSvgCanvas2D.prototype.lineHeightCorrection = 1;

/**
 * Variable: pointerEventsValue
 * 
 * Default value for active pointer events. Default is all.
 */
mxSvgCanvas2D.prototype.pointerEventsValue = 'all';

/**
 * Variable: fontMetricsPadding
 * 
 * Padding to be added for text that is not wrapped to account for differences
 * in font metrics on different platforms in pixels. Default is 10.
 */
mxSvgCanvas2D.prototype.fontMetricsPadding = 10;

/**
 * Variable: cacheOffsetSize
 * 
 * Specifies if offsetWidth and offsetHeight should be cached. Default is true.
 * This is used to speed up repaint of text in .
 */
mxSvgCanvas2D.prototype.cacheOffsetSize = true;

/**
 * Function: format
 * 
 * Rounds all numbers to 2 decimal points.
 */
mxSvgCanvas2D.prototype.format = function(value)
{
	return parseFloat(parseFloat(value).toFixed(2));
};

/**
 * Function: getBaseUrl
 * 
 * Returns the URL of the page without the hash part. This needs to use href to
 * include any search part with no params (ie question mark alone). This is a
 * workaround for the fact that window.location.search is empty if there is
 * no search string behind the question mark.
 */
mxSvgCanvas2D.prototype.getBaseUrl = function()
{
	var href = window.location.href;
	var hash = href.lastIndexOf('#');
	
	if (hash > 0)
	{
		href = href.substring(0, hash);
	}
	
	return href;
};

/**
 * Function: reset
 * 
 * Returns any offsets for rendering pixels.
 */
mxSvgCanvas2D.prototype.reset = function()
{
	mxAbstractCanvas2D.prototype.reset.apply(this, arguments);
	this.gradients = [];
};

/**
 * Function: createStyle
 * 
 * Creates the optional style section.
 */
mxSvgCanvas2D.prototype.createStyle = function(x)
{
	var style = this.createElement('style');
	style.setAttribute('type', 'text/css');
	mxUtils.write(style, 'svg{font-family:' + mxConstants.DEFAULT_FONTFAMILY +
			';font-size:' + mxConstants.DEFAULT_FONTSIZE +
			';fill:none;stroke-miterlimit:10}');
	
	return style;
};

/**
 * Function: createElement
 * 
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.createElement = function(tagName, namespace)
{
	if (this.root.ownerDocument.createElementNS != null)
	{
		return this.root.ownerDocument.createElementNS(namespace || mxConstants.NS_SVG, tagName);
	}
	else
	{
		var elt = this.root.ownerDocument.createElement(tagName);
		
		if (namespace != null)
		{
			elt.setAttribute('xmlns', namespace);
		}
		
		return elt;
	}
};

/**
 * Function: getAlternateText
 * 
 * Returns the alternate text string for the given foreignObject.
 */
mxSvgCanvas2D.prototype.getAlternateText = function(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)
{
	return (str != null) ? this.foAltText : null;
};

/**
 * Function: getAlternateContent
 * 
 * Returns the alternate content for the given foreignObject.
 */
mxSvgCanvas2D.prototype.createAlternateContent = function(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation)
{
	var text = this.getAlternateText(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation);
	var s = this.state;

	if (text != null && s.fontSize > 0)
	{
		var dy = (valign == mxConstants.ALIGN_TOP) ? 1 :
			(valign == mxConstants.ALIGN_BOTTOM) ? 0 : 0.3;
		var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' :
			(align == mxConstants.ALIGN_LEFT) ? 'start' :
			'middle';
	
		var alt = this.createElement('text');
		alt.setAttribute('x', Math.round(x + s.dx));
		alt.setAttribute('y', Math.round(y + s.dy + dy * s.fontSize));
		alt.setAttribute('fill', s.fontColor || 'black');
		alt.setAttribute('font-family', s.fontFamily);
		alt.setAttribute('font-size', Math.round(s.fontSize) + 'px');

		// Text-anchor start is default in SVG
		if (anchor != 'start')
		{
			alt.setAttribute('text-anchor', anchor);
		}
		
		if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
		{
			alt.setAttribute('font-weight', 'bold');
		}
		
		if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
		{
			alt.setAttribute('font-style', 'italic');
		}
		
		var txtDecor = [];
		
		if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
		{
			txtDecor.push('underline');
		}
		
		if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH)
		{
			txtDecor.push('line-through');
		}
		
		if (txtDecor.length > 0)
		{
			alt.setAttribute('text-decoration', txtDecor.join(' '));
		}
		
		mxUtils.write(alt, text);
		
		return alt;
	}
	else
	{
		return null;
	}
};

/**
 * Function: createGradientId
 * 
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.createGradientId = function(start, end, alpha1, alpha2, direction)
{
	// Removes illegal characters from gradient ID
	if (start.charAt(0) == '#')
	{
		start = start.substring(1);
	}
	
	if (end.charAt(0) == '#')
	{
		end = end.substring(1);
	}
	
	// Workaround for gradient IDs not working in Safari 5 / Chrome 6
	// if they contain uppercase characters
	start = start.toLowerCase() + '-' + alpha1;
	end = end.toLowerCase() + '-' + alpha2;

	// Wrong gradient directions possible?
	var dir = null;
	
	if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
	{
		dir = 's';
	}
	else if (direction == mxConstants.DIRECTION_EAST)
	{
		dir = 'e';
	}
	else
	{
		var tmp = start;
		start = end;
		end = tmp;
		
		if (direction == mxConstants.DIRECTION_NORTH)
		{
			dir = 's';
		}
		else if (direction == mxConstants.DIRECTION_WEST)
		{
			dir = 'e';
		}
	}
	
	return 'mx-gradient-' + start + '-' + end + '-' + dir;
};

/**
 * Function: getSvgGradient
 * 
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.getSvgGradient = function(start, end, alpha1, alpha2, direction)
{
	var id = this.createGradientId(start, end, alpha1, alpha2, direction);
	var gradient = this.gradients[id];
	
	if (gradient == null)
	{
		var svg = this.root.ownerSVGElement;

		var counter = 0;
		var tmpId = id + '-' + counter;

		if (svg != null)
		{
			gradient = svg.ownerDocument.getElementById(tmpId);
			
			while (gradient != null && gradient.ownerSVGElement != svg)
			{
				tmpId = id + '-' + counter++;
				gradient = svg.ownerDocument.getElementById(tmpId);
			}
		}
		else
		{
			// Uses shorter IDs for export
			tmpId = 'id' + (++this.refCount);
		}
		
		if (gradient == null)
		{
			gradient = this.createSvgGradient(start, end, alpha1, alpha2, direction);
			gradient.setAttribute('id', tmpId);
			
			if (this.defs != null)
			{
				this.defs.appendChild(gradient);
			}
			else
			{
				svg.appendChild(gradient);
			}
		}

		this.gradients[id] = gradient;
	}

	return gradient.getAttribute('id');
};

/**
 * Function: createSvgGradient
 * 
 * Creates the given SVG gradient.
 */
mxSvgCanvas2D.prototype.createSvgGradient = function(start, end, alpha1, alpha2, direction)
{
	var gradient = this.createElement('linearGradient');
	gradient.setAttribute('x1', '0%');
	gradient.setAttribute('y1', '0%');
	gradient.setAttribute('x2', '0%');
	gradient.setAttribute('y2', '0%');
	
	if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
	{
		gradient.setAttribute('y2', '100%');
	}
	else if (direction == mxConstants.DIRECTION_EAST)
	{
		gradient.setAttribute('x2', '100%');
	}
	else if (direction == mxConstants.DIRECTION_NORTH)
	{
		gradient.setAttribute('y1', '100%');
	}
	else if (direction == mxConstants.DIRECTION_WEST)
	{
		gradient.setAttribute('x1', '100%');
	}
	
	var op = (alpha1 < 1) ? ';stop-opacity:' + alpha1 : '';
	
	var stop = this.createElement('stop');
	stop.setAttribute('offset', '0%');
	stop.setAttribute('style', 'stop-color:' + start + op);
	gradient.appendChild(stop);
	
	op = (alpha2 < 1) ? ';stop-opacity:' + alpha2 : '';
	
	stop = this.createElement('stop');
	stop.setAttribute('offset', '100%');
	stop.setAttribute('style', 'stop-color:' + end + op);
	gradient.appendChild(stop);
	
	return gradient;
};

/**
 * Function: addNode
 * 
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.addNode = function(filled, stroked)
{
	var node = this.node;
	var s = this.state;

	if (node != null)
	{
		if (node.nodeName == 'path')
		{
			// Checks if the path is not empty
			if (this.path != null && this.path.length > 0)
			{
				node.setAttribute('d', this.path.join(' '));
			}
			else
			{
				return;
			}
		}

		if (filled && s.fillColor != null)
		{
			this.updateFill();
		}
		else if (!this.styleEnabled)
		{
			// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=814952
			if (node.nodeName == 'ellipse' && mxClient.IS_FF)
			{
				node.setAttribute('fill', 'transparent');
			}
			else
			{
				node.setAttribute('fill', 'none');
			}
			
			// Sets the actual filled state for stroke tolerance
			filled = false;
		}
		
		if (stroked && s.strokeColor != null)
		{
			this.updateStroke();
		}
		else if (!this.styleEnabled)
		{
			node.setAttribute('stroke', 'none');
		}
		
		if (s.transform != null && s.transform.length > 0)
		{
			node.setAttribute('transform', s.transform);
		}
		
		if (s.shadow)
		{
			this.root.appendChild(this.createShadow(node));
		}
	
		// Adds stroke tolerance
		if (this.strokeTolerance > 0 && !filled)
		{
			this.root.appendChild(this.createTolerance(node));
		}

		// Adds pointer events
		if (this.pointerEvents)
		{
			node.setAttribute('pointer-events', this.pointerEventsValue);
		}
		// Enables clicks for nodes inside a link element
		else if (!this.pointerEvents && this.originalRoot == null)
		{
			node.setAttribute('pointer-events', 'none');
		}
		
		// Removes invisible nodes from output if they don't handle events
		if ((node.nodeName != 'rect' && node.nodeName != 'path' && node.nodeName != 'ellipse') ||
			(node.getAttribute('fill') != 'none' && node.getAttribute('fill') != 'transparent') ||
			node.getAttribute('stroke') != 'none' || node.getAttribute('pointer-events') != 'none')
		{
			// LATER: Update existing DOM for performance		
			this.root.appendChild(node);
		}
		
		this.node = null;
	}
};

/**
 * Function: updateFill
 * 
 * Transfers the stroke attributes from  to .
 */
mxSvgCanvas2D.prototype.updateFill = function()
{
	var s = this.state;
	
	if (s.alpha < 1 || s.fillAlpha < 1)
	{
		this.node.setAttribute('fill-opacity', s.alpha * s.fillAlpha);
	}
	
	if (s.fillColor != null)
	{
		if (s.gradientColor != null)
		{
			var id = this.getSvgGradient(String(s.fillColor), String(s.gradientColor),
				s.gradientFillAlpha, s.gradientAlpha, s.gradientDirection);
			
			if (!mxClient.IS_CHROMEAPP && !mxClient.IS_IE && !mxClient.IS_IE11 &&
				!mxClient.IS_EDGE && this.root.ownerDocument == document)
			{
				// Workaround for potential base tag and brackets must be escaped
				var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1');
				this.node.setAttribute('fill', 'url(' + base + '#' + id + ')');
			}
			else
			{
				this.node.setAttribute('fill', 'url(#' + id + ')');
			}
		}
		else
		{
			this.node.setAttribute('fill', String(s.fillColor).toLowerCase());
		}
	}
};

/**
 * Function: getCurrentStrokeWidth
 * 
 * Returns the current stroke width (>= 1), ie. max(1, this.format(this.state.strokeWidth * this.state.scale)).
 */
mxSvgCanvas2D.prototype.getCurrentStrokeWidth = function()
{
	return Math.max(this.minStrokeWidth, Math.max(0.01, this.format(this.state.strokeWidth * this.state.scale)));
};

/**
 * Function: updateStroke
 * 
 * Transfers the stroke attributes from  to .
 */
mxSvgCanvas2D.prototype.updateStroke = function()
{
	var s = this.state;

	this.node.setAttribute('stroke', String(s.strokeColor).toLowerCase());
	
	if (s.alpha < 1 || s.strokeAlpha < 1)
	{
		this.node.setAttribute('stroke-opacity', s.alpha * s.strokeAlpha);
	}
	
	var sw = this.getCurrentStrokeWidth();
	
	if (sw != 1)
	{
		this.node.setAttribute('stroke-width', sw);
	}
	
	if (this.node.nodeName == 'path')
	{
		this.updateStrokeAttributes();
	}
	
	if (s.dashed)
	{
		this.node.setAttribute('stroke-dasharray', this.createDashPattern(
			((s.fixDash) ? 1 : s.strokeWidth) * s.scale));
	}
};

/**
 * Function: updateStrokeAttributes
 * 
 * Transfers the stroke attributes from  to .
 */
mxSvgCanvas2D.prototype.updateStrokeAttributes = function()
{
	var s = this.state;
	
	// Linejoin miter is default in SVG
	if (s.lineJoin != null && s.lineJoin != 'miter')
	{
		this.node.setAttribute('stroke-linejoin', s.lineJoin);
	}
	
	if (s.lineCap != null)
	{
		// flat is called butt in SVG
		var value = s.lineCap;
		
		if (value == 'flat')
		{
			value = 'butt';
		}
		
		// Linecap butt is default in SVG
		if (value != 'butt')
		{
			this.node.setAttribute('stroke-linecap', value);
		}
	}
	
	// Miterlimit 10 is default in our document
	if (s.miterLimit != null && (!this.styleEnabled || s.miterLimit != 10))
	{
		this.node.setAttribute('stroke-miterlimit', s.miterLimit);
	}
};

/**
 * Function: createDashPattern
 * 
 * Creates the SVG dash pattern for the given state.
 */
mxSvgCanvas2D.prototype.createDashPattern = function(scale)
{
	var pat = [];
	
	if (typeof(this.state.dashPattern) === 'string')
	{
		var dash = this.state.dashPattern.split(' ');
		
		if (dash.length > 0)
		{
			for (var i = 0; i < dash.length; i++)
			{
				pat[i] = Number(dash[i]) * scale;
			}
		}
	}
	
	return pat.join(' ');
};

/**
 * Function: createTolerance
 * 
 * Creates a hit detection tolerance shape for the given node.
 */
mxSvgCanvas2D.prototype.createTolerance = function(node)
{
	var tol = node.cloneNode(true);
	var sw = parseFloat(tol.getAttribute('stroke-width') || 1) + this.strokeTolerance;
	tol.setAttribute('pointer-events', 'stroke');
	tol.setAttribute('visibility', 'hidden');
	tol.removeAttribute('stroke-dasharray');
	tol.setAttribute('stroke-width', sw);
	tol.setAttribute('fill', 'none');
	
	// Workaround for Opera ignoring the visiblity attribute above while
	// other browsers need a stroke color to perform the hit-detection but
	// do not ignore the visibility attribute. Side-effect is that Opera's
	// hit detection for horizontal/vertical edges seems to ignore the tol.
	tol.setAttribute('stroke', (mxClient.IS_OT) ? 'none' : 'white');
	
	return tol;
};

/**
 * Function: createShadow
 * 
 * Creates a shadow for the given node.
 */
mxSvgCanvas2D.prototype.createShadow = function(node)
{
	var shadow = node.cloneNode(true);
	var s = this.state;

	// Firefox uses transparent for no fill in ellipses
	if (shadow.getAttribute('fill') != 'none' && (!mxClient.IS_FF || shadow.getAttribute('fill') != 'transparent'))
	{
		shadow.setAttribute('fill', s.shadowColor);
	}
	
	if (shadow.getAttribute('stroke') != 'none')
	{
		shadow.setAttribute('stroke', s.shadowColor);
	}

	shadow.setAttribute('transform', 'translate(' + this.format(s.shadowDx * s.scale) +
		',' + this.format(s.shadowDy * s.scale) + ')' + (s.transform || ''));
	shadow.setAttribute('opacity', s.shadowAlpha);
	
	return shadow;
};

/**
 * Function: setLink
 * 
 * Experimental implementation for hyperlinks.
 */
mxSvgCanvas2D.prototype.setLink = function(link)
{
	if (link == null)
	{
		this.root = this.originalRoot;
	}
	else
	{
		this.originalRoot = this.root;
		
		var node = this.createElement('a');
		
		// Workaround for implicit namespace handling in HTML5 export, IE adds NS1 namespace so use code below
		// in all IE versions except quirks mode. KNOWN: Adds xlink namespace to each image tag in output.
		if (node.setAttributeNS == null || (this.root.ownerDocument != document && document.documentMode == null))
		{
			node.setAttribute('xlink:href', link);
		}
		else
		{
			node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', link);
		}
		
		this.root.appendChild(node);
		this.root = node;
	}
};

/**
 * Function: rotate
 * 
 * Sets the rotation of the canvas. Note that rotation cannot be concatenated.
 */
mxSvgCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy)
{
	if (theta != 0 || flipH || flipV)
	{
		var s = this.state;
		cx += s.dx;
		cy += s.dy;
	
		cx *= s.scale;
		cy *= s.scale;

		s.transform = s.transform || '';
		
		// This implementation uses custom scale/translate and built-in rotation
		// Rotation state is part of the AffineTransform in state.transform
		if (flipH && flipV)
		{
			theta += 180;
		}
		else if (flipH != flipV)
		{
			var tx = (flipH) ? cx : 0;
			var sx = (flipH) ? -1 : 1;
	
			var ty = (flipV) ? cy : 0;
			var sy = (flipV) ? -1 : 1;

			s.transform += 'translate(' + this.format(tx) + ',' + this.format(ty) + ')' +
				'scale(' + this.format(sx) + ',' + this.format(sy) + ')' +
				'translate(' + this.format(-tx) + ',' + this.format(-ty) + ')';
		}
		
		if (flipH ? !flipV : flipV)
		{
			theta *= -1;
		}
		
		if (theta != 0)
		{
			s.transform += 'rotate(' + this.format(theta) + ',' + this.format(cx) + ',' + this.format(cy) + ')';
		}
		
		s.rotation = s.rotation + theta;
		s.rotationCx = cx;
		s.rotationCy = cy;
	}
};

/**
 * Function: begin
 * 
 * Extends superclass to create path.
 */
mxSvgCanvas2D.prototype.begin = function()
{
	mxAbstractCanvas2D.prototype.begin.apply(this, arguments);
	this.node = this.createElement('path');
};

/**
 * Function: rect
 * 
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.rect = function(x, y, w, h)
{
	var s = this.state;
	var n = this.createElement('rect');
	n.setAttribute('x', this.format((x + s.dx) * s.scale));
	n.setAttribute('y', this.format((y + s.dy) * s.scale));
	n.setAttribute('width', this.format(w * s.scale));
	n.setAttribute('height', this.format(h * s.scale));
	
	this.node = n;
};

/**
 * Function: roundrect
 * 
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy)
{
	this.rect(x, y, w, h);
	
	if (dx > 0)
	{
		this.node.setAttribute('rx', this.format(dx * this.state.scale));
	}
	
	if (dy > 0)
	{
		this.node.setAttribute('ry', this.format(dy * this.state.scale));
	}
};

/**
 * Function: ellipse
 * 
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.ellipse = function(x, y, w, h)
{
	var s = this.state;
	var n = this.createElement('ellipse');
	// No rounding for consistent output with 1.x
	n.setAttribute('cx', this.format((x + w / 2 + s.dx) * s.scale));
	n.setAttribute('cy', this.format((y + h / 2 + s.dy) * s.scale));
	n.setAttribute('rx', w / 2 * s.scale);
	n.setAttribute('ry', h / 2 * s.scale);
	this.node = n;
};

/**
 * Function: image
 * 
 * Private helper function to create SVG elements
 */
mxSvgCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV)
{
	src = this.converter.convert(src);
	
	// LATER: Add option for embedding images as base64.
	aspect = (aspect != null) ? aspect : true;
	flipH = (flipH != null) ? flipH : false;
	flipV = (flipV != null) ? flipV : false;
	
	var s = this.state;
	x += s.dx;
	y += s.dy;
	
	var node = this.createElement('image');
	node.setAttribute('x', this.format(x * s.scale) + this.imageOffset);
	node.setAttribute('y', this.format(y * s.scale) + this.imageOffset);
	node.setAttribute('width', this.format(w * s.scale));
	node.setAttribute('height', this.format(h * s.scale));
	
	// Workaround for missing namespace support
	if (node.setAttributeNS == null)
	{
		node.setAttribute('xlink:href', src);
	}
	else
	{
		node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
	}
	
	if (!aspect)
	{
		node.setAttribute('preserveAspectRatio', 'none');
	}

	if (s.alpha < 1 || s.fillAlpha < 1)
	{
		node.setAttribute('opacity', s.alpha * s.fillAlpha);
	}
	
	var tr = this.state.transform || '';
	
	if (flipH || flipV)
	{
		var sx = 1;
		var sy = 1;
		var dx = 0;
		var dy = 0;
		
		if (flipH)
		{
			sx = -1;
			dx = -w - 2 * x;
		}
		
		if (flipV)
		{
			sy = -1;
			dy = -h - 2 * y;
		}
		
		// Adds image tansformation to existing transform
		tr += 'scale(' + sx + ',' + sy + ')translate(' + (dx * s.scale) + ',' + (dy * s.scale) + ')';
	}

	if (tr.length > 0)
	{
		node.setAttribute('transform', tr);
	}
	
	if (!this.pointerEvents)
	{
		node.setAttribute('pointer-events', 'none');
	}
	
	this.root.appendChild(node);
};

/**
 * Function: convertHtml
 * 
 * Converts the given HTML string to XHTML.
 */
mxSvgCanvas2D.prototype.convertHtml = function(val)
{
	if (this.useDomParser)
	{
		var doc = new DOMParser().parseFromString(val, 'text/html');

		if (doc != null)
		{
			val = new XMLSerializer().serializeToString(doc.body);
			
			// Extracts body content from DOM
			if (val.substring(0, 5) == '', 5) + 1);
			}
			
			if (val.substring(val.length - 7, val.length) == '')
			{
				val = val.substring(0, val.length - 7);
			}
		}
	}
	else if (document.implementation != null && document.implementation.createDocument != null)
	{
		var xd = document.implementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null);
		var xb = xd.createElement('body');
		xd.documentElement.appendChild(xb);
		
		var div = document.createElement('div');
		div.innerHTML = val;
		var child = div.firstChild;
		
		while (child != null)
		{
			var next = child.nextSibling;
			xb.appendChild(xd.adoptNode(child));
			child = next;
		}
		
		return xb.innerHTML;
	}
	else
	{
		var ta = document.createElement('textarea');
		
		// Handles special HTML entities < and > and double escaping
		// and converts unclosed br, hr and img tags to XHTML
		// LATER: Convert all unclosed tags
		ta.innerHTML = val.replace(/&/g, '&amp;').
			replace(/</g, '&lt;').replace(/>/g, '&gt;').
			replace(/</g, '&lt;').replace(/>/g, '&gt;').
			replace(//g, '>');
		val = ta.value.replace(/&/g, '&').replace(/&lt;/g, '<').
			replace(/&gt;/g, '>').replace(/&amp;/g, '&').
			replace(/
/g, '
').replace(/
/g, '
'). replace(/(]+)>/gm, "$1 />"); } return val; }; /** * Function: createDiv * * Private helper function to create SVG elements */ mxSvgCanvas2D.prototype.createDiv = function(str) { var val = str; if (!mxUtils.isNode(val)) { val = '
' + this.convertHtml(val) + '
'; } // IE uses this code for export as it cannot render foreignObjects if (!mxClient.IS_IE && !mxClient.IS_IE11 && document.createElementNS) { var div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div'); if (mxUtils.isNode(val)) { var div2 = document.createElement('div'); var div3 = div2.cloneNode(false); // Creates a copy for export if (this.root.ownerDocument != document) { div2.appendChild(val.cloneNode(true)); } else { div2.appendChild(val); } div3.appendChild(div2); div.appendChild(div3); } else { div.innerHTML = val; } return div; } else { if (mxUtils.isNode(val)) { val = '
' + mxUtils.getXml(val) + '
'; } val = '
' + val + '
'; // NOTE: FF 3.6 crashes if content CSS contains "height:100%" return mxUtils.parseXml(val).documentElement; } }; /** * Updates existing DOM nodes for text rendering. LATER: Merge common parts with text function below. */ mxSvgCanvas2D.prototype.updateText = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node) { if (node != null && node.firstChild != null && node.firstChild.firstChild != null) { this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, node.firstChild); } }; /** * Function: addForeignObject * * Creates a foreignObject for the given string and adds it to the given root. */ mxSvgCanvas2D.prototype.addForeignObject = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir, div, root) { var group = this.createElement('g'); var fo = this.createElement('foreignObject'); // Workarounds for print clipping and static position in Safari fo.setAttribute('style', 'overflow: visible; text-align: left;'); fo.setAttribute('pointer-events', 'none'); // Import needed for older versions of IE if (div.ownerDocument != document) { div = mxUtils.importNodeImplementation(fo.ownerDocument, div, true); } fo.appendChild(div); group.appendChild(fo); this.updateTextNodes(x, y, w, h, align, valign, wrap, overflow, clip, rotation, group); // Alternate content if foreignObject not supported if (this.root.ownerDocument != document) { var alt = this.createAlternateContent(fo, x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation); if (alt != null) { fo.setAttribute('requiredFeatures', 'http://www.w3.org/TR/SVG11/feature#Extensibility'); var sw = this.createElement('switch'); sw.appendChild(fo); sw.appendChild(alt); group.appendChild(sw); } } root.appendChild(group); }; /** * Updates existing DOM nodes for text rendering. */ mxSvgCanvas2D.prototype.updateTextNodes = function(x, y, w, h, align, valign, wrap, overflow, clip, rotation, g) { var s = this.state.scale; mxSvgCanvas2D.createCss(w + 2, h, align, valign, wrap, overflow, clip, (this.state.fontBackgroundColor != null) ? this.state.fontBackgroundColor : null, (this.state.fontBorderColor != null) ? this.state.fontBorderColor : null, 'display: flex; align-items: unsafe ' + ((valign == mxConstants.ALIGN_TOP) ? 'flex-start' : ((valign == mxConstants.ALIGN_BOTTOM) ? 'flex-end' : 'center')) + '; ' + 'justify-content: unsafe ' + ((align == mxConstants.ALIGN_LEFT) ? 'flex-start' : ((align == mxConstants.ALIGN_RIGHT) ? 'flex-end' : 'center')) + '; ', this.getTextCss(), s, mxUtils.bind(this, function(dx, dy, flex, item, block) { x += this.state.dx; y += this.state.dy; var fo = g.firstChild; var div = fo.firstChild; var box = div.firstChild; var text = box.firstChild; var r = ((this.rotateHtml) ? this.state.rotation : 0) + ((rotation != null) ? rotation : 0); var t = ((this.foOffset != 0) ? 'translate(' + this.foOffset + ' ' + this.foOffset + ')' : '') + ((s != 1) ? 'scale(' + s + ')' : ''); text.setAttribute('style', block); box.setAttribute('style', item); // Workaround for clipping in Webkit with scrolling and zoom fo.setAttribute('width', Math.ceil(1 / Math.min(1, s) * 100) + '%'); fo.setAttribute('height', Math.ceil(1 / Math.min(1, s) * 100) + '%'); var yp = Math.round(y + dy); // Allows for negative values which are causing problems with // transformed content where the top edge of the foreignObject // limits the text box being moved further up in the diagram. // KNOWN: Possible clipping problems with zoom and scrolling // but this is normally not used with scrollbars as the // coordinates are always positive with scrollbars. // Margin-top is ignored in Safari and no negative values allowed // for padding. if (yp < 0) { fo.setAttribute('y', yp); } else { fo.removeAttribute('y'); flex += 'padding-top: ' + yp + 'px; '; } div.setAttribute('style', flex + 'margin-left: ' + Math.round(x + dx) + 'px;'); t += ((r != 0) ? ('rotate(' + r + ' ' + x + ' ' + y + ')') : ''); // Output allows for reflow but Safari cannot use absolute position, // transforms or opacity. https://bugs.webkit.org/show_bug.cgi?id=23113 if (t != '') { g.setAttribute('transform', t); } else { g.removeAttribute('transform'); } if (this.state.alpha != 1) { g.setAttribute('opacity', this.state.alpha); } else { g.removeAttribute('opacity'); } })); }; /** * Updates existing DOM nodes for text rendering. */ mxSvgCanvas2D.createCss = function(w, h, align, valign, wrap, overflow, clip, bg, border, flex, block, s, callback) { var item = 'box-sizing: border-box; font-size: 0; text-align: ' + ((align == mxConstants.ALIGN_LEFT) ? 'left' : ((align == mxConstants.ALIGN_RIGHT) ? 'right' : 'center')) + '; '; var pt = mxUtils.getAlignmentAsPoint(align, valign); var ofl = 'overflow: hidden; '; var fw = 'width: 1px; '; var fh = 'height: 1px; '; var dx = pt.x * w; var dy = pt.y * h; if (clip) { fw = 'width: ' + Math.round(w) + 'px; '; item += 'max-height: ' + Math.round(h) + 'px; '; dy = 0; } else if (overflow == 'fill') { fw = 'width: ' + Math.round(w) + 'px; '; fh = 'height: ' + Math.round(h) + 'px; '; block += 'width: 100%; height: 100%; '; item += fw + fh; } else if (overflow == 'width') { fw = 'width: ' + Math.round(w) + 'px; '; block += 'width: 100%; '; item += fw; dy = 0; if (h > 0) { item += 'max-height: ' + Math.round(h) + 'px; '; } } else { ofl = ''; dy = 0; } var bgc = ''; if (bg != null) { bgc += 'background-color: ' + bg + '; '; } if (border != null) { bgc += 'border: 1px solid ' + border + '; '; } if (ofl == '' || clip) { block += bgc; } else { item += bgc; } if (wrap && w > 0) { block += 'white-space: normal; word-wrap: ' + mxConstants.WORD_WRAP + '; '; fw = 'width: ' + Math.round(w) + 'px; '; if (ofl != '' && overflow != 'fill') { dy = 0; } } else { block += 'white-space: nowrap; '; if (ofl == '') { dx = 0; } } callback(dx, dy, flex + fw + fh, item + ofl, block, ofl); }; /** * Function: getTextCss * * Private helper function to create SVG elements */ mxSvgCanvas2D.prototype.getTextCss = function() { var s = this.state; var lh = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : (mxConstants.LINE_HEIGHT * this.lineHeightCorrection); var css = 'display: inline-block; font-size: ' + s.fontSize + 'px; ' + 'font-family: ' + s.fontFamily + '; color: ' + s.fontColor + '; line-height: ' + lh + '; pointer-events: ' + ((this.pointerEvents) ? this.pointerEventsValue : 'none') + '; '; if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { css += 'font-weight: bold; '; } if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { css += 'font-style: italic; '; } var deco = []; if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { deco.push('underline'); } if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) { deco.push('line-through'); } if (deco.length > 0) { css += 'text-decoration: ' + deco.join(' ') + '; '; } return css; }; /** * Function: text * * Paints the given text. Possible values for format are empty string for plain * text and html for HTML markup. Note that HTML markup is only supported if * foreignObject is supported and is true. (This means IE9 and later * does currently not support HTML text as part of shapes.) */ mxSvgCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) { if (this.textEnabled && str != null) { rotation = (rotation != null) ? rotation : 0; if (this.foEnabled && format == 'html') { var div = this.createDiv(str); // Ignores invalid XHTML labels if (div != null) { if (dir != null) { div.setAttribute('dir', dir); } this.addForeignObject(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir, div, this.root); } } else { this.plainText(x + this.state.dx, y + this.state.dy, w, h, str, align, valign, wrap, overflow, clip, rotation, dir); } } }; /** * Function: createClip * * Creates a clip for the given coordinates. */ mxSvgCanvas2D.prototype.createClip = function(x, y, w, h) { x = Math.round(x); y = Math.round(y); w = Math.round(w); h = Math.round(h); var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h; var counter = 0; var tmp = id + '-' + counter; // Resolves ID conflicts while (document.getElementById(tmp) != null) { tmp = id + '-' + (++counter); } clip = this.createElement('clipPath'); clip.setAttribute('id', tmp); var rect = this.createElement('rect'); rect.setAttribute('x', x); rect.setAttribute('y', y); rect.setAttribute('width', w); rect.setAttribute('height', h); clip.appendChild(rect); return clip; }; /** * Function: text * * Paints the given text. Possible values for format are empty string for * plain text and html for HTML markup. */ mxSvgCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, overflow, clip, rotation, dir) { rotation = (rotation != null) ? rotation : 0; var s = this.state; var size = s.fontSize; var node = this.createElement('g'); var tr = s.transform || ''; this.updateFont(node); // Non-rotated text if (rotation != 0) { tr += 'rotate(' + rotation + ',' + this.format(x * s.scale) + ',' + this.format(y * s.scale) + ')'; } if (dir != null) { node.setAttribute('direction', dir); } if (clip && w > 0 && h > 0) { var cx = x; var cy = y; if (align == mxConstants.ALIGN_CENTER) { cx -= w / 2; } else if (align == mxConstants.ALIGN_RIGHT) { cx -= w; } if (overflow != 'fill') { if (valign == mxConstants.ALIGN_MIDDLE) { cy -= h / 2; } else if (valign == mxConstants.ALIGN_BOTTOM) { cy -= h; } } // LATER: Remove spacing from clip rectangle var c = this.createClip(cx * s.scale - 2, cy * s.scale - 2, w * s.scale + 4, h * s.scale + 4); if (this.defs != null) { this.defs.appendChild(c); } else { // Makes sure clip is removed with referencing node this.root.appendChild(c); } if (!mxClient.IS_CHROMEAPP && !mxClient.IS_IE && !mxClient.IS_IE11 && !mxClient.IS_EDGE && this.root.ownerDocument == document) { // Workaround for potential base tag var base = this.getBaseUrl().replace(/([\(\)])/g, '\\$1'); node.setAttribute('clip-path', 'url(' + base + '#' + c.getAttribute('id') + ')'); } else { node.setAttribute('clip-path', 'url(#' + c.getAttribute('id') + ')'); } } // Default is left var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' : (align == mxConstants.ALIGN_CENTER) ? 'middle' : 'start'; // Text-anchor start is default in SVG if (anchor != 'start') { node.setAttribute('text-anchor', anchor); } if (!this.styleEnabled || size != mxConstants.DEFAULT_FONTSIZE) { node.setAttribute('font-size', (size * s.scale) + 'px'); } if (tr.length > 0) { node.setAttribute('transform', tr); } if (s.alpha < 1) { node.setAttribute('opacity', s.alpha); } var lines = str.split('\n'); var lh = Math.round(size * mxConstants.LINE_HEIGHT); var textHeight = size + (lines.length - 1) * lh; var cy = y + size - 1; if (valign == mxConstants.ALIGN_MIDDLE) { if (overflow == 'fill') { cy -= h / 2; } else { var dy = ((this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight) / 2; cy -= dy; } } else if (valign == mxConstants.ALIGN_BOTTOM) { if (overflow == 'fill') { cy -= h; } else { var dy = (this.matchHtmlAlignment && clip && h > 0) ? Math.min(textHeight, h) : textHeight; cy -= dy + 1; } } for (var i = 0; i < lines.length; i++) { // Workaround for bounding box of empty lines and spaces if (lines[i].length > 0 && mxUtils.trim(lines[i]).length > 0) { var text = this.createElement('text'); // LATER: Match horizontal HTML alignment text.setAttribute('x', this.format(x * s.scale) + this.textOffset); text.setAttribute('y', this.format(cy * s.scale) + this.textOffset); mxUtils.write(text, lines[i]); node.appendChild(text); } cy += lh; } this.root.appendChild(node); this.addTextBackground(node, str, x, y, w, (overflow == 'fill') ? h : textHeight, align, valign, overflow); }; /** * Function: updateFont * * Updates the text properties for the given node. (NOTE: For this to work in * IE, the given node must be a text or tspan element.) */ mxSvgCanvas2D.prototype.updateFont = function(node) { var s = this.state; node.setAttribute('fill', s.fontColor); if (!this.styleEnabled || s.fontFamily != mxConstants.DEFAULT_FONTFAMILY) { node.setAttribute('font-family', s.fontFamily); } if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { node.setAttribute('font-weight', 'bold'); } if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { node.setAttribute('font-style', 'italic'); } var txtDecor = []; if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { txtDecor.push('underline'); } if ((s.fontStyle & mxConstants.FONT_STRIKETHROUGH) == mxConstants.FONT_STRIKETHROUGH) { txtDecor.push('line-through'); } if (txtDecor.length > 0) { node.setAttribute('text-decoration', txtDecor.join(' ')); } }; /** * Function: addTextBackground * * Background color and border */ mxSvgCanvas2D.prototype.addTextBackground = function(node, str, x, y, w, h, align, valign, overflow) { var s = this.state; if (s.fontBackgroundColor != null || s.fontBorderColor != null) { var bbox = null; if (overflow == 'fill' || overflow == 'width') { if (align == mxConstants.ALIGN_CENTER) { x -= w / 2; } else if (align == mxConstants.ALIGN_RIGHT) { x -= w; } if (valign == mxConstants.ALIGN_MIDDLE) { y -= h / 2; } else if (valign == mxConstants.ALIGN_BOTTOM) { y -= h; } bbox = new mxRectangle((x + 1) * s.scale, y * s.scale, (w - 2) * s.scale, (h + 2) * s.scale); } else if (node.getBBox != null && this.root.ownerDocument == document) { // Uses getBBox only if inside document for correct size try { bbox = node.getBBox(); var ie = mxClient.IS_IE && mxClient.IS_SVG; bbox = new mxRectangle(bbox.x, bbox.y + ((ie) ? 0 : 1), bbox.width, bbox.height + ((ie) ? 1 : 0)); } catch (e) { // Ignores NS_ERROR_FAILURE in FF if container display is none. } } else { // Computes size if not in document or no getBBox available var div = document.createElement('div'); // Wrapping and clipping can be ignored here div.style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (s.fontSize * mxConstants.LINE_HEIGHT) + 'px' : mxConstants.LINE_HEIGHT; div.style.fontSize = s.fontSize + 'px'; div.style.fontFamily = s.fontFamily; div.style.whiteSpace = 'nowrap'; div.style.position = 'absolute'; div.style.visibility = 'hidden'; div.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block'; div.style.zoom = '1'; if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { div.style.fontWeight = 'bold'; } if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { div.style.fontStyle = 'italic'; } str = mxUtils.htmlEntities(str, false); div.innerHTML = str.replace(/\n/g, '
'); document.body.appendChild(div); var w = div.offsetWidth; var h = div.offsetHeight; div.parentNode.removeChild(div); if (align == mxConstants.ALIGN_CENTER) { x -= w / 2; } else if (align == mxConstants.ALIGN_RIGHT) { x -= w; } if (valign == mxConstants.ALIGN_MIDDLE) { y -= h / 2; } else if (valign == mxConstants.ALIGN_BOTTOM) { y -= h; } bbox = new mxRectangle((x + 1) * s.scale, (y + 2) * s.scale, w * s.scale, (h + 1) * s.scale); } if (bbox != null) { var n = this.createElement('rect'); n.setAttribute('fill', s.fontBackgroundColor || 'none'); n.setAttribute('stroke', s.fontBorderColor || 'none'); n.setAttribute('x', Math.floor(bbox.x - 1)); n.setAttribute('y', Math.floor(bbox.y - 1)); n.setAttribute('width', Math.ceil(bbox.width + 2)); n.setAttribute('height', Math.ceil(bbox.height)); var sw = (s.fontBorderColor != null) ? Math.max(1, this.format(s.scale)) : 0; n.setAttribute('stroke-width', sw); // Workaround for crisp rendering - only required if not exporting if (this.root.ownerDocument == document && mxUtils.mod(sw, 2) == 1) { n.setAttribute('transform', 'translate(0.5, 0.5)'); } node.insertBefore(n, node.firstChild); } } }; /** * Function: stroke * * Paints the outline of the current path. */ mxSvgCanvas2D.prototype.stroke = function() { this.addNode(false, true); }; /** * Function: fill * * Fills the current path. */ mxSvgCanvas2D.prototype.fill = function() { this.addNode(true, false); }; /** * Function: fillAndStroke * * Fills and paints the outline of the current path. */ mxSvgCanvas2D.prototype.fillAndStroke = function() { this.addNode(true, true); }; /** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * * Class: mxVmlCanvas2D * * Implements a canvas to be used for rendering VML. Here is an example of implementing a * fallback for SVG images which are not supported in VML-based browsers. * * (code) * var mxVmlCanvas2DImage = mxVmlCanvas2D.prototype.image; * mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV) * { * if (src.substring(src.length - 4, src.length) == '.svg') * { * src = 'http://www.jgraph.com/images/mxgraph.gif'; * } * * mxVmlCanvas2DImage.apply(this, arguments); * }; * (end) * * To disable anti-aliasing in the output, use the following code. * * (code) * document.createStyleSheet().cssText = mxClient.VML_PREFIX + '\\:*{antialias:false;)}'; * (end) * * A description of the public API is available in . Note that * there is a known issue in VML where gradients are painted using the outer * bounding box of rotated shapes, not the actual bounds of the shape. See * also for plain text label restrictions in shapes for VML. */ var mxVmlCanvas2D = function(root) { mxAbstractCanvas2D.call(this); /** * Variable: root * * Reference to the container for the SVG content. */ this.root = root; }; /** * Extends mxAbstractCanvas2D */ mxUtils.extend(mxVmlCanvas2D, mxAbstractCanvas2D); /** * Variable: path * * Holds the current DOM node. */ mxVmlCanvas2D.prototype.node = null; /** * Variable: textEnabled * * Specifies if text output should be enabledetB. Default is true. */ mxVmlCanvas2D.prototype.textEnabled = true; /** * Variable: moveOp * * Contains the string used for moving in paths. Default is 'm'. */ mxVmlCanvas2D.prototype.moveOp = 'm'; /** * Variable: lineOp * * Contains the string used for moving in paths. Default is 'l'. */ mxVmlCanvas2D.prototype.lineOp = 'l'; /** * Variable: curveOp * * Contains the string used for bezier curves. Default is 'c'. */ mxVmlCanvas2D.prototype.curveOp = 'c'; /** * Variable: closeOp * * Holds the operator for closing curves. Default is 'x e'. */ mxVmlCanvas2D.prototype.closeOp = 'x'; /** * Variable: rotatedHtmlBackground * * Background color for rotated HTML. Default is ''. This can be set to eg. * white to improve rendering of rotated text in VML for IE9. */ mxVmlCanvas2D.prototype.rotatedHtmlBackground = ''; /** * Variable: vmlScale * * Specifies the scale used to draw VML shapes. */ mxVmlCanvas2D.prototype.vmlScale = 1; /** * Function: createElement * * Creates the given element using the document. */ mxVmlCanvas2D.prototype.createElement = function(name) { return document.createElement(name); }; /** * Function: createVmlElement * * Creates a new element using and prefixes the given name with * . */ mxVmlCanvas2D.prototype.createVmlElement = function(name) { return this.createElement(mxClient.VML_PREFIX + ':' + name); }; /** * Function: addNode * * Adds the current node to the . */ mxVmlCanvas2D.prototype.addNode = function(filled, stroked) { var node = this.node; var s = this.state; if (node != null) { if (node.nodeName == 'shape') { // Checks if the path is not empty if (this.path != null && this.path.length > 0) { node.path = this.path.join(' ') + ' e'; node.style.width = this.root.style.width; node.style.height = this.root.style.height; node.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height); } else { return; } } node.strokeweight = this.format(Math.max(1, s.strokeWidth * s.scale / this.vmlScale)) + 'px'; if (s.shadow) { this.root.appendChild(this.createShadow(node, filled && s.fillColor != null, stroked && s.strokeColor != null)); } if (stroked && s.strokeColor != null) { node.stroked = 'true'; node.strokecolor = s.strokeColor; } else { node.stroked = 'false'; } node.appendChild(this.createStroke()); if (filled && s.fillColor != null) { node.appendChild(this.createFill()); } else if (this.pointerEvents && (node.nodeName != 'shape' || this.path[this.path.length - 1] == this.closeOp)) { node.appendChild(this.createTransparentFill()); } else { node.filled = 'false'; } // LATER: Update existing DOM for performance this.root.appendChild(node); } }; /** * Function: createTransparentFill * * Creates a transparent fill. */ mxVmlCanvas2D.prototype.createTransparentFill = function() { var fill = this.createVmlElement('fill'); fill.src = mxClient.imageBasePath + '/transparent.gif'; fill.type = 'tile'; return fill; }; /** * Function: createFill * * Creates a fill for the current state. */ mxVmlCanvas2D.prototype.createFill = function() { var s = this.state; // Gradients in foregrounds not supported because special gradients // with bounds must be created for each element in graphics-canvases var fill = this.createVmlElement('fill'); fill.color = s.fillColor; if (s.gradientColor != null) { fill.type = 'gradient'; fill.method = 'none'; fill.color2 = s.gradientColor; var angle = 180 - s.rotation; if (s.gradientDirection == mxConstants.DIRECTION_WEST) { angle -= 90 + ((this.root.style.flip == 'x') ? 180 : 0); } else if (s.gradientDirection == mxConstants.DIRECTION_EAST) { angle += 90 + ((this.root.style.flip == 'x') ? 180 : 0); } else if (s.gradientDirection == mxConstants.DIRECTION_NORTH) { angle -= 180 + ((this.root.style.flip == 'y') ? -180 : 0); } else { angle += ((this.root.style.flip == 'y') ? -180 : 0); } if (this.root.style.flip == 'x' || this.root.style.flip == 'y') { angle *= -1; } // LATER: Fix outer bounding box for rotated shapes used in VML. fill.angle = mxUtils.mod(angle, 360); fill.opacity = (s.alpha * s.gradientFillAlpha * 100) + '%'; fill.setAttribute(mxClient.OFFICE_PREFIX + ':opacity2', (s.alpha * s.gradientAlpha * 100) + '%'); } else if (s.alpha < 1 || s.fillAlpha < 1) { fill.opacity = (s.alpha * s.fillAlpha * 100) + '%'; } return fill; }; /** * Function: createStroke * * Creates a fill for the current state. */ mxVmlCanvas2D.prototype.createStroke = function() { var s = this.state; var stroke = this.createVmlElement('stroke'); stroke.endcap = s.lineCap || 'flat'; stroke.joinstyle = s.lineJoin || 'miter'; stroke.miterlimit = s.miterLimit || '10'; if (s.alpha < 1 || s.strokeAlpha < 1) { stroke.opacity = (s.alpha * s.strokeAlpha * 100) + '%'; } if (s.dashed) { stroke.dashstyle = this.getVmlDashStyle(); } return stroke; }; /** * Function: getVmlDashPattern * * Returns a VML dash pattern for the current dashPattern. * See http://msdn.microsoft.com/en-us/library/bb264085(v=vs.85).aspx */ mxVmlCanvas2D.prototype.getVmlDashStyle = function() { var result = 'dash'; if (typeof(this.state.dashPattern) === 'string') { var tok = this.state.dashPattern.split(' '); if (tok.length > 0 && tok[0] == 1) { result = '0 2'; } } return result; }; /** * Function: createShadow * * Creates a shadow for the given node. */ mxVmlCanvas2D.prototype.createShadow = function(node, filled, stroked) { var s = this.state; var rad = -s.rotation * (Math.PI / 180); var cos = Math.cos(rad); var sin = Math.sin(rad); var dx = s.shadowDx * s.scale; var dy = s.shadowDy * s.scale; if (this.root.style.flip == 'x') { dx *= -1; } else if (this.root.style.flip == 'y') { dy *= -1; } var shadow = node.cloneNode(true); shadow.style.marginLeft = Math.round(dx * cos - dy * sin) + 'px'; shadow.style.marginTop = Math.round(dx * sin + dy * cos) + 'px'; // Workaround for wrong cloning in IE8 standards mode if (document.documentMode == 8) { shadow.strokeweight = node.strokeweight; if (node.nodeName == 'shape') { shadow.path = this.path.join(' ') + ' e'; shadow.style.width = this.root.style.width; shadow.style.height = this.root.style.height; shadow.coordsize = parseInt(node.style.width) + ' ' + parseInt(node.style.height); } } if (stroked) { shadow.strokecolor = s.shadowColor; shadow.appendChild(this.createShadowStroke()); } else { shadow.stroked = 'false'; } if (filled) { shadow.appendChild(this.createShadowFill()); } else { shadow.filled = 'false'; } return shadow; }; /** * Function: createShadowFill * * Creates the fill for the shadow. */ mxVmlCanvas2D.prototype.createShadowFill = function() { var fill = this.createVmlElement('fill'); fill.color = this.state.shadowColor; fill.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%'; return fill; }; /** * Function: createShadowStroke * * Creates the stroke for the shadow. */ mxVmlCanvas2D.prototype.createShadowStroke = function() { var stroke = this.createStroke(); stroke.opacity = (this.state.alpha * this.state.shadowAlpha * 100) + '%'; return stroke; }; /** * Function: rotate * * Sets the rotation of the canvas. Note that rotation cannot be concatenated. */ mxVmlCanvas2D.prototype.rotate = function(theta, flipH, flipV, cx, cy) { if (flipH && flipV) { theta += 180; } else if (flipH) { this.root.style.flip = 'x'; } else if (flipV) { this.root.style.flip = 'y'; } if (flipH ? !flipV : flipV) { theta *= -1; } this.root.style.rotation = theta; this.state.rotation = this.state.rotation + theta; this.state.rotationCx = cx; this.state.rotationCy = cy; }; /** * Function: begin * * Extends superclass to create path. */ mxVmlCanvas2D.prototype.begin = function() { mxAbstractCanvas2D.prototype.begin.apply(this, arguments); this.node = this.createVmlElement('shape'); this.node.style.position = 'absolute'; }; /** * Function: quadTo * * Replaces quadratic curve with bezier curve in VML. */ mxVmlCanvas2D.prototype.quadTo = function(x1, y1, x2, y2) { var s = this.state; var cpx0 = (this.lastX + s.dx) * s.scale; var cpy0 = (this.lastY + s.dy) * s.scale; var qpx1 = (x1 + s.dx) * s.scale; var qpy1 = (y1 + s.dy) * s.scale; var cpx3 = (x2 + s.dx) * s.scale; var cpy3 = (y2 + s.dy) * s.scale; var cpx1 = cpx0 + 2/3 * (qpx1 - cpx0); var cpy1 = cpy0 + 2/3 * (qpy1 - cpy0); var cpx2 = cpx3 + 2/3 * (qpx1 - cpx3); var cpy2 = cpy3 + 2/3 * (qpy1 - cpy3); this.path.push('c ' + this.format(cpx1) + ' ' + this.format(cpy1) + ' ' + this.format(cpx2) + ' ' + this.format(cpy2) + ' ' + this.format(cpx3) + ' ' + this.format(cpy3)); this.lastX = (cpx3 / s.scale) - s.dx; this.lastY = (cpy3 / s.scale) - s.dy; }; /** * Function: createRect * * Sets the glass gradient. */ mxVmlCanvas2D.prototype.createRect = function(nodeName, x, y, w, h) { var s = this.state; var n = this.createVmlElement(nodeName); n.style.position = 'absolute'; n.style.left = this.format((x + s.dx) * s.scale) + 'px'; n.style.top = this.format((y + s.dy) * s.scale) + 'px'; n.style.width = this.format(w * s.scale) + 'px'; n.style.height = this.format(h * s.scale) + 'px'; return n; }; /** * Function: rect * * Sets the current path to a rectangle. */ mxVmlCanvas2D.prototype.rect = function(x, y, w, h) { this.node = this.createRect('rect', x, y, w, h); }; /** * Function: roundrect * * Sets the current path to a rounded rectangle. */ mxVmlCanvas2D.prototype.roundrect = function(x, y, w, h, dx, dy) { this.node = this.createRect('roundrect', x, y, w, h); // SetAttribute needed here for IE8 this.node.setAttribute('arcsize', Math.max(dx * 100 / w, dy * 100 / h) + '%'); }; /** * Function: ellipse * * Sets the current path to an ellipse. */ mxVmlCanvas2D.prototype.ellipse = function(x, y, w, h) { this.node = this.createRect('oval', x, y, w, h); }; /** * Function: image * * Paints an image. */ mxVmlCanvas2D.prototype.image = function(x, y, w, h, src, aspect, flipH, flipV) { var node = null; if (!aspect) { node = this.createRect('image', x, y, w, h); node.src = src; } else { // Uses fill with aspect to avoid asynchronous update of size node = this.createRect('rect', x, y, w, h); node.stroked = 'false'; // Handles image aspect via fill var fill = this.createVmlElement('fill'); fill.aspect = (aspect) ? 'atmost' : 'ignore'; fill.rotate = 'true'; fill.type = 'frame'; fill.src = src; node.appendChild(fill); } if (flipH && flipV) { node.style.rotation = '180'; } else if (flipH) { node.style.flip = 'x'; } else if (flipV) { node.style.flip = 'y'; } if (this.state.alpha < 1 || this.state.fillAlpha < 1) { // KNOWN: Borders around transparent images in IE<9. Using fill.opacity // fixes this problem by adding a white background in all IE versions. node.style.filter += 'alpha(opacity=' + (this.state.alpha * this.state.fillAlpha * 100) + ')'; } this.root.appendChild(node); }; /** * Function: createText * * Creates the innermost element that contains the HTML text. */ mxVmlCanvas2D.prototype.createDiv = function(str, align, valign, overflow) { var div = this.createElement('div'); var state = this.state; var css = ''; if (state.fontBackgroundColor != null) { css += 'background-color:' + mxUtils.htmlEntities(state.fontBackgroundColor) + ';'; } if (state.fontBorderColor != null) { css += 'border:1px solid ' + mxUtils.htmlEntities(state.fontBorderColor) + ';'; } if (mxUtils.isNode(str)) { div.appendChild(str); } else { if (overflow != 'fill' && overflow != 'width') { var div2 = this.createElement('div'); div2.style.cssText = css; div2.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block'; div2.style.zoom = '1'; div2.style.textDecoration = 'inherit'; div2.innerHTML = str; div.appendChild(div2); } else { div.style.cssText = css; div.innerHTML = str; } } var style = div.style; style.fontSize = (state.fontSize / this.vmlScale) + 'px'; style.fontFamily = state.fontFamily; style.color = state.fontColor; style.verticalAlign = 'top'; style.textAlign = align || 'left'; style.lineHeight = (mxConstants.ABSOLUTE_LINE_HEIGHT) ? (state.fontSize * mxConstants.LINE_HEIGHT / this.vmlScale) + 'px' : mxConstants.LINE_HEIGHT; if ((state.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { style.fontWeight = 'bold'; } if ((state.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { style.fontStyle = 'italic'; } if ((state.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { style.textDecoration = 'underline'; } return div; }; /** * Function: text * * Paints the given text. Possible values for format are empty string for plain * text and html for HTML markup. Clipping, text background and border are not * supported for plain text in VML. */ mxVmlCanvas2D.prototype.text = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) { if (this.textEnabled && str != null) { var s = this.state; if (format == 'html') { if (s.rotation != null) { var pt = this.rotatePoint(x, y, s.rotation, s.rotationCx, s.rotationCy); x = pt.x; y = pt.y; } if (document.documentMode == 8 && !mxClient.IS_EM) { x += s.dx; y += s.dy; // Workaround for rendering offsets if (overflow != 'fill' && valign == mxConstants.ALIGN_TOP) { y -= 1; } } else { x *= s.scale; y *= s.scale; } // Adds event transparency in IE8 standards without the transparent background // filter which cannot be used due to bugs in the zoomed bounding box (too slow) // FIXME: No event transparency if inside v:rect (ie part of shape) // KNOWN: Offset wrong for rotated text with word that are longer than the wrapping // width in IE8 because real width of text cannot be determined here. // This should be fixed in mxText.updateBoundingBox by calling before this and // passing the real width to this method if not clipped and wrapped. var abs = (document.documentMode == 8 && !mxClient.IS_EM) ? this.createVmlElement('group') : this.createElement('div'); abs.style.position = 'absolute'; abs.style.display = 'inline'; abs.style.left = this.format(x) + 'px'; abs.style.top = this.format(y) + 'px'; abs.style.zoom = s.scale; var box = this.createElement('div'); box.style.position = 'relative'; box.style.display = 'inline'; var margin = mxUtils.getAlignmentAsPoint(align, valign); var dx = margin.x; var dy = margin.y; var div = this.createDiv(str, align, valign, overflow); var inner = this.createElement('div'); if (dir != null) { div.setAttribute('dir', dir); } if (wrap && w > 0) { if (!clip) { div.style.width = Math.round(w) + 'px'; } div.style.wordWrap = mxConstants.WORD_WRAP; div.style.whiteSpace = 'normal'; // LATER: Check if other cases need to be handled if (div.style.wordWrap == 'break-word') { var tmp = div; if (tmp.firstChild != null && tmp.firstChild.nodeName == 'DIV') { tmp.firstChild.style.width = '100%'; } } } else { div.style.whiteSpace = 'nowrap'; } var rot = s.rotation + (rotation || 0); if (this.rotateHtml && rot != 0) { inner.style.display = 'inline'; inner.style.zoom = '1'; inner.appendChild(div); // Box not needed for rendering in IE8 standards if (document.documentMode == 8 && !mxClient.IS_EM && this.root.nodeName != 'DIV') { box.appendChild(inner); abs.appendChild(box); } else { abs.appendChild(inner); } } else if (document.documentMode == 8 && !mxClient.IS_EM) { box.appendChild(div); abs.appendChild(box); } else { div.style.display = 'inline'; abs.appendChild(div); } // Inserts the node into the DOM if (this.root.nodeName != 'DIV') { // Rectangle to fix position in group var rect = this.createVmlElement('rect'); rect.stroked = 'false'; rect.filled = 'false'; rect.appendChild(abs); this.root.appendChild(rect); } else { this.root.appendChild(abs); } if (clip) { div.style.overflow = 'hidden'; div.style.width = Math.round(w) + 'px'; if (!mxClient.IS_QUIRKS) { div.style.maxHeight = Math.round(h) + 'px'; } } else if (overflow == 'fill') { // KNOWN: Affects horizontal alignment in quirks // but fill should only be used with align=left div.style.overflow = 'hidden'; div.style.width = (Math.max(0, w) + 1) + 'px'; div.style.height = (Math.max(0, h) + 1) + 'px'; } else if (overflow == 'width') { // KNOWN: Affects horizontal alignment in quirks // but fill should only be used with align=left div.style.overflow = 'hidden'; div.style.width = (Math.max(0, w) + 1) + 'px'; div.style.maxHeight = (Math.max(0, h) + 1) + 'px'; } if (this.rotateHtml && rot != 0) { var rad = rot * (Math.PI / 180); // Precalculate cos and sin for the rotation var real_cos = parseFloat(parseFloat(Math.cos(rad)).toFixed(8)); var real_sin = parseFloat(parseFloat(Math.sin(-rad)).toFixed(8)); rad %= 2 * Math.PI; if (rad < 0) rad += 2 * Math.PI; rad %= Math.PI; if (rad > Math.PI / 2) rad = Math.PI - rad; var cos = Math.cos(rad); var sin = Math.sin(rad); // Adds div to document to measure size if (document.documentMode == 8 && !mxClient.IS_EM) { div.style.display = 'inline-block'; inner.style.display = 'inline-block'; box.style.display = 'inline-block'; } div.style.visibility = 'hidden'; div.style.position = 'absolute'; document.body.appendChild(div); var sizeDiv = div; if (sizeDiv.firstChild != null && sizeDiv.firstChild.nodeName == 'DIV') { sizeDiv = sizeDiv.firstChild; } var tmp = sizeDiv.offsetWidth + 3; var oh = sizeDiv.offsetHeight; if (clip) { w = Math.min(w, tmp); oh = Math.min(oh, h); } else { w = tmp; } // Handles words that are longer than the given wrapping width if (wrap) { div.style.width = w + 'px'; } // Simulates max-height in quirks if (mxClient.IS_QUIRKS && (clip || overflow == 'width') && oh > h) { oh = h; // Quirks does not support maxHeight div.style.height = oh + 'px'; } h = oh; var top_fix = (h - h * cos + w * -sin) / 2 - real_sin * w * (dx + 0.5) + real_cos * h * (dy + 0.5); var left_fix = (w - w * cos + h * -sin) / 2 + real_cos * w * (dx + 0.5) + real_sin * h * (dy + 0.5); if (abs.nodeName == 'group' && this.root.nodeName == 'DIV') { // Workaround for bug where group gets moved away if left and top are non-zero in IE8 standards var pos = this.createElement('div'); pos.style.display = 'inline-block'; pos.style.position = 'absolute'; pos.style.left = this.format(x + (left_fix - w / 2) * s.scale) + 'px'; pos.style.top = this.format(y + (top_fix - h / 2) * s.scale) + 'px'; abs.parentNode.appendChild(pos); pos.appendChild(abs); } else { var sc = (document.documentMode == 8 && !mxClient.IS_EM) ? 1 : s.scale; abs.style.left = this.format(x + (left_fix - w / 2) * sc) + 'px'; abs.style.top = this.format(y + (top_fix - h / 2) * sc) + 'px'; } // KNOWN: Rotated text rendering quality is bad for IE9 quirks inner.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11="+real_cos+", M12="+ real_sin+", M21="+(-real_sin)+", M22="+real_cos+", sizingMethod='auto expand')"; inner.style.backgroundColor = this.rotatedHtmlBackground; if (this.state.alpha < 1) { inner.style.filter += 'alpha(opacity=' + (this.state.alpha * 100) + ')'; } // Restore parent node for DIV inner.appendChild(div); div.style.position = ''; div.style.visibility = ''; } else if (document.documentMode != 8 || mxClient.IS_EM) { div.style.verticalAlign = 'top'; if (this.state.alpha < 1) { abs.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')'; } // Adds div to document to measure size var divParent = div.parentNode; div.style.visibility = 'hidden'; document.body.appendChild(div); w = div.offsetWidth; var oh = div.offsetHeight; // Simulates max-height in quirks if (mxClient.IS_QUIRKS && clip && oh > h) { oh = h; // Quirks does not support maxHeight div.style.height = oh + 'px'; } h = oh; div.style.visibility = ''; divParent.appendChild(div); abs.style.left = this.format(x + w * dx * this.state.scale) + 'px'; abs.style.top = this.format(y + h * dy * this.state.scale) + 'px'; } else { if (this.state.alpha < 1) { div.style.filter = 'alpha(opacity=' + (this.state.alpha * 100) + ')'; } // Faster rendering in IE8 without offsetWidth/Height box.style.left = (dx * 100) + '%'; box.style.top = (dy * 100) + '%'; } } else { this.plainText(x, y, w, h, mxUtils.htmlEntities(str, false), align, valign, wrap, format, overflow, clip, rotation, dir); } } }; /** * Function: plainText * * Paints the outline of the current path. */ mxVmlCanvas2D.prototype.plainText = function(x, y, w, h, str, align, valign, wrap, format, overflow, clip, rotation, dir) { // TextDirection is ignored since this code is not used (format is always HTML in the text function) var s = this.state; x = (x + s.dx) * s.scale; y = (y + s.dy) * s.scale; var node = this.createVmlElement('shape'); node.style.width = '1px'; node.style.height = '1px'; node.stroked = 'false'; var fill = this.createVmlElement('fill'); fill.color = s.fontColor; fill.opacity = (s.alpha * 100) + '%'; node.appendChild(fill); var path = this.createVmlElement('path'); path.textpathok = 'true'; path.v = 'm ' + this.format(0) + ' ' + this.format(0) + ' l ' + this.format(1) + ' ' + this.format(0); node.appendChild(path); // KNOWN: Font family and text decoration ignored var tp = this.createVmlElement('textpath'); tp.style.cssText = 'v-text-align:' + align; tp.style.align = align; tp.style.fontFamily = s.fontFamily; tp.string = str; tp.on = 'true'; // Scale via fontsize instead of node.style.zoom for correct offsets in IE8 var size = s.fontSize * s.scale / this.vmlScale; tp.style.fontSize = size + 'px'; // Bold if ((s.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD) { tp.style.fontWeight = 'bold'; } // Italic if ((s.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC) { tp.style.fontStyle = 'italic'; } // Underline if ((s.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE) { tp.style.textDecoration = 'underline'; } var lines = str.split('\n'); var textHeight = size + (lines.length - 1) * size * mxConstants.LINE_HEIGHT; var dx = 0; var dy = 0; if (valign == mxConstants.ALIGN_BOTTOM) { dy = - textHeight / 2; } else if (valign != mxConstants.ALIGN_MIDDLE) // top { dy = textHeight / 2; } if (rotation != null) { node.style.rotation = rotation; var rad = rotation * (Math.PI / 180); dx = Math.sin(rad) * dy; dy = Math.cos(rad) * dy; } // FIXME: Clipping is relative to bounding box /*if (clip) { node.style.clip = 'rect(0px ' + this.format(w) + 'px ' + this.format(h) + 'px 0px)'; }*/ node.appendChild(tp); node.style.left = this.format(x - dx) + 'px'; node.style.top = this.format(y + dy) + 'px'; this.root.appendChild(node); }; /** * Function: stroke * * Paints the outline of the current path. */ mxVmlCanvas2D.prototype.stroke = function() { this.addNode(false, true); }; /** * Function: fill * * Fills the current path. */ mxVmlCanvas2D.prototype.fill = function() { this.addNode(true, false); }; /** * Function: fillAndStroke * * Fills and paints the outline of the current path. */ mxVmlCanvas2D.prototype.fillAndStroke = function() { this.addNode(true, true); }; /** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * Class: mxGuide * * Implements the alignment of selection cells to other cells in the graph. * * Constructor: mxGuide * * Constructs a new guide object. */ function mxGuide(graph, states) { this.graph = graph; this.setStates(states); }; /** * Variable: graph * * Reference to the enclosing instance. */ mxGuide.prototype.graph = null; /** * Variable: states * * Contains the that are used for alignment. */ mxGuide.prototype.states = null; /** * Variable: horizontal * * Specifies if horizontal guides are enabled. Default is true. */ mxGuide.prototype.horizontal = true; /** * Variable: vertical * * Specifies if vertical guides are enabled. Default is true. */ mxGuide.prototype.vertical = true; /** * Variable: vertical * * Holds the for the horizontal guide. */ mxGuide.prototype.guideX = null; /** * Variable: vertical * * Holds the for the vertical guide. */ mxGuide.prototype.guideY = null; /** * Variable: rounded * * Specifies if rounded coordinates should be used. Default is false. */ mxGuide.prototype.rounded = false; /** * Variable: tolerance * * Default tolerance in px if grid is disabled. Default is 2. */ mxGuide.prototype.tolerance = 2; /** * Function: setStates * * Sets the that should be used for alignment. */ mxGuide.prototype.setStates = function(states) { this.states = states; }; /** * Function: isEnabledForEvent * * Returns true if the guide should be enabled for the given native event. This * implementation always returns true. */ mxGuide.prototype.isEnabledForEvent = function(evt) { return true; }; /** * Function: getGuideTolerance * * Returns the tolerance for the guides. Default value is gridSize / 2. */ mxGuide.prototype.getGuideTolerance = function(gridEnabled) { return (gridEnabled && this.graph.gridEnabled) ? this.graph.gridSize / 2 : this.tolerance; }; /** * Function: createGuideShape * * Returns the mxShape to be used for painting the respective guide. This * implementation returns a new, dashed and crisp using * and as the format. * * Parameters: * * horizontal - Boolean that specifies which guide should be created. */ mxGuide.prototype.createGuideShape = function(horizontal) { var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH); guide.isDashed = true; return guide; }; /** * Function: isStateIgnored * * Returns true if the given state should be ignored. */ mxGuide.prototype.isStateIgnored = function(state) { return false; }; /** * Function: move * * Moves the by the given and returnt the snapped point. */ mxGuide.prototype.move = function(bounds, delta, gridEnabled, clone) { if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null) { var trx = this.graph.getView().translate; var scale = this.graph.getView().scale; var tt = this.getGuideTolerance(gridEnabled) * scale; var b = bounds.clone(); b.x += delta.x; b.y += delta.y; var overrideX = false; var stateX = null; var valueX = null; var overrideY = false; var stateY = null; var valueY = null; var ttX = tt; var ttY = tt; var left = b.x; var right = b.x + b.width; var center = b.getCenterX(); var top = b.y; var bottom = b.y + b.height; var middle = b.getCenterY(); // Snaps the left, center and right to the given x-coordinate function snapX(x, state, centerAlign) { var override = false; if (centerAlign && Math.abs(x - center) < ttX) { delta.x = x - bounds.getCenterX(); ttX = Math.abs(x - center); override = true; } else if (!centerAlign) { if (Math.abs(x - left) < ttX) { delta.x = x - bounds.x; ttX = Math.abs(x - left); override = true; } else if (Math.abs(x - right) < ttX) { delta.x = x - bounds.x - bounds.width; ttX = Math.abs(x - right); override = true; } } if (override) { stateX = state; valueX = x; if (this.guideX == null) { this.guideX = this.createGuideShape(true); // Makes sure to use either VML or SVG shapes in order to implement // event-transparency on the background area of the rectangle since // HTML shapes do not let mouseevents through even when transparent this.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; this.guideX.pointerEvents = false; this.guideX.init(this.graph.getView().getOverlayPane()); } } overrideX = overrideX || override; }; // Snaps the top, middle or bottom to the given y-coordinate function snapY(y, state, centerAlign) { var override = false; if (centerAlign && Math.abs(y - middle) < ttY) { delta.y = y - bounds.getCenterY(); ttY = Math.abs(y - middle); override = true; } else if (!centerAlign) { if (Math.abs(y - top) < ttY) { delta.y = y - bounds.y; ttY = Math.abs(y - top); override = true; } else if (Math.abs(y - bottom) < ttY) { delta.y = y - bounds.y - bounds.height; ttY = Math.abs(y - bottom); override = true; } } if (override) { stateY = state; valueY = y; if (this.guideY == null) { this.guideY = this.createGuideShape(false); // Makes sure to use either VML or SVG shapes in order to implement // event-transparency on the background area of the rectangle since // HTML shapes do not let mouseevents through even when transparent this.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ? mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG; this.guideY.pointerEvents = false; this.guideY.init(this.graph.getView().getOverlayPane()); } } overrideY = overrideY || override; }; for (var i = 0; i < this.states.length; i++) { var state = this.states[i]; if (state != null && !this.isStateIgnored(state)) { // Align x if (this.horizontal) { snapX.call(this, state.getCenterX(), state, true); snapX.call(this, state.x, state, false); snapX.call(this, state.x + state.width, state, false); // Aligns left and right of shape to center of page if (state.cell == null) { snapX.call(this, state.getCenterX(), state, false); } } // Align y if (this.vertical) { snapY.call(this, state.getCenterY(), state, true); snapY.call(this, state.y, state, false); snapY.call(this, state.y + state.height, state, false); // Aligns left and right of shape to center of page if (state.cell == null) { snapY.call(this, state.getCenterY(), state, false); } } } } // Moves cells to the raster if not aligned this.graph.snapDelta(delta, bounds, !gridEnabled, overrideX, overrideY); delta = this.getDelta(bounds, stateX, delta.x, stateY, delta.y) // Redraws the guides var c = this.graph.container; if (!overrideX && this.guideX != null) { this.guideX.node.style.visibility = 'hidden'; } else if (this.guideX != null) { var minY = null; var maxY = null; if (stateX != null && bounds != null) { minY = Math.min(bounds.y + delta.y - this.graph.panDy, stateX.y); maxY = Math.max(bounds.y + bounds.height + delta.y - this.graph.panDy, stateX.y + stateX.height); } if (minY != null && maxY != null) { this.guideX.points = [new mxPoint(valueX, minY), new mxPoint(valueX, maxY)]; } else { this.guideX.points = [new mxPoint(valueX, -this.graph.panDy), new mxPoint(valueX, c.scrollHeight - 3 - this.graph.panDy)]; } this.guideX.stroke = this.getGuideColor(stateX, true); this.guideX.node.style.visibility = 'visible'; this.guideX.redraw(); } if (!overrideY && this.guideY != null) { this.guideY.node.style.visibility = 'hidden'; } else if (this.guideY != null) { var minX = null; var maxX = null; if (stateY != null && bounds != null) { minX = Math.min(bounds.x + delta.x - this.graph.panDx, stateY.x); maxX = Math.max(bounds.x + bounds.width + delta.x - this.graph.panDx, stateY.x + stateY.width); } if (minX != null && maxX != null) { this.guideY.points = [new mxPoint(minX, valueY), new mxPoint(maxX, valueY)]; } else { this.guideY.points = [new mxPoint(-this.graph.panDx, valueY), new mxPoint(c.scrollWidth - 3 - this.graph.panDx, valueY)]; } this.guideY.stroke = this.getGuideColor(stateY, false); this.guideY.node.style.visibility = 'visible'; this.guideY.redraw(); } } return delta; }; /** * Function: getDelta * * Rounds to pixels for virtual states (eg. page guides) */ mxGuide.prototype.getDelta = function(bounds, stateX, dx, stateY, dy) { var s = this.graph.view.scale; if (this.rounded || (stateX != null && stateX.cell == null)) { dx = Math.round((bounds.x + dx) / s) * s - bounds.x; } if (this.rounded || (stateY != null && stateY.cell == null)) { dy = Math.round((bounds.y + dy) / s) * s - bounds.y; } return new mxPoint(dx, dy); }; /** * Function: getGuideColor * * Returns the color for the given state. */ mxGuide.prototype.getGuideColor = function(state, horizontal) { return mxConstants.GUIDE_COLOR; }; /** * Function: hide * * Hides all current guides. */ mxGuide.prototype.hide = function() { this.setVisible(false); }; /** * Function: setVisible * * Shows or hides the current guides. */ mxGuide.prototype.setVisible = function(visible) { if (this.guideX != null) { this.guideX.node.style.visibility = (visible) ? 'visible' : 'hidden'; } if (this.guideY != null) { this.guideY.node.style.visibility = (visible) ? 'visible' : 'hidden'; } }; /** * Function: destroy * * Destroys all resources that this object uses. */ mxGuide.prototype.destroy = function() { if (this.guideX != null) { this.guideX.destroy(); this.guideX = null; } if (this.guideY != null) { this.guideY.destroy(); this.guideY = null; } }; /** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * Class: mxShape * * Base class for all shapes. A shape in mxGraph is a * separate implementation for SVG, VML and HTML. Which * implementation to use is controlled by the * property which is assigned from within the * when the shape is created. The dialect must be assigned * for a shape, and it does normally depend on the browser and * the confiuration of the graph (see rendering hint). * * For each supported shape in SVG and VML, a corresponding * shape exists in mxGraph, namely for text, image, rectangle, * rhombus, ellipse and polyline. The other shapes are a * combination of these shapes (eg. label and swimlane) * or they consist of one or more (filled) path objects * (eg. actor and cylinder). The HTML implementation is * optional but may be required for a HTML-only view of * the graph. * * Custom Shapes: * * To extend from this class, the basic code looks as follows. * In the special case where the custom shape consists only of * one filled region or one filled region and an additional stroke * the and should be subclassed, * respectively. * * (code) * function CustomShape() { } * * CustomShape.prototype = new mxShape(); * CustomShape.prototype.constructor = CustomShape; * (end) * * To register a custom shape in an existing graph instance, * one must register the shape under a new name in the graph's * cell renderer as follows: * * (code) * mxCellRenderer.registerShape('customShape', CustomShape); * (end) * * The second argument is the name of the constructor. * * In order to use the shape you can refer to the given name above * in a stylesheet. For example, to change the shape for the default * vertex style, the following code is used: * * (code) * var style = graph.getStylesheet().getDefaultVertexStyle(); * style[mxConstants.STYLE_SHAPE] = 'customShape'; * (end) * * Constructor: mxShape * * Constructs a new shape. */ function mxShape(stencil) { this.stencil = stencil; this.initStyles(); }; /** * Variable: dialect * * Holds the dialect in which the shape is to be painted. * This can be one of the DIALECT constants in . */ mxShape.prototype.dialect = null; /** * Variable: scale * * Holds the scale in which the shape is being painted. */ mxShape.prototype.scale = 1; /** * Variable: antiAlias * * Rendering hint for configuring the canvas. */ mxShape.prototype.antiAlias = true; /** * Variable: minSvgStrokeWidth * * Minimum stroke width for SVG output. */ mxShape.prototype.minSvgStrokeWidth = 1; /** * Variable: bounds * * Holds the that specifies the bounds of this shape. */ mxShape.prototype.bounds = null; /** * Variable: points * * Holds the array of that specify the points of this shape. */ mxShape.prototype.points = null; /** * Variable: node * * Holds the outermost DOM node that represents this shape. */ mxShape.prototype.node = null; /** * Variable: state * * Optional reference to the corresponding . */ mxShape.prototype.state = null; /** * Variable: style * * Optional reference to the style of the corresponding . */ mxShape.prototype.style = null; /** * Variable: boundingBox * * Contains the bounding box of the shape, that is, the smallest rectangle * that includes all pixels of the shape. */ mxShape.prototype.boundingBox = null; /** * Variable: stencil * * Holds the that defines the shape. */ mxShape.prototype.stencil = null; /** * Variable: svgStrokeTolerance * * Event-tolerance for SVG strokes (in px). Default is 8. This is only passed * to the canvas in if is true. */ mxShape.prototype.svgStrokeTolerance = 8; /** * Variable: pointerEvents * * Specifies if pointer events should be handled. Default is true. */ mxShape.prototype.pointerEvents = true; /** * Variable: svgPointerEvents * * Specifies if pointer events should be handled. Default is true. */ mxShape.prototype.svgPointerEvents = 'all'; /** * Variable: shapePointerEvents * * Specifies if pointer events outside of shape should be handled. Default * is false. */ mxShape.prototype.shapePointerEvents = false; /** * Variable: stencilPointerEvents * * Specifies if pointer events outside of stencils should be handled. Default * is false. Set this to true for backwards compatibility with the 1.x branch. */ mxShape.prototype.stencilPointerEvents = false; /** * Variable: vmlScale * * Scale for improving the precision of VML rendering. Default is 1. */ mxShape.prototype.vmlScale = 1; /** * Variable: outline * * Specifies if the shape should be drawn as an outline. This disables all * fill colors and can be used to disable other drawing states that should * not be painted for outlines. Default is false. This should be set before * calling . */ mxShape.prototype.outline = false; /** * Variable: visible * * Specifies if the shape is visible. Default is true. */ mxShape.prototype.visible = true; /** * Variable: useSvgBoundingBox * * Allows to use the SVG bounding box in SVG. Default is false for performance * reasons. */ mxShape.prototype.useSvgBoundingBox = false; /** * Function: init * * Initializes the shape by creaing the DOM node using * and adding it into the given container. * * Parameters: * * container - DOM node that will contain the shape. */ mxShape.prototype.init = function(container) { if (this.node == null) { this.node = this.create(container); if (container != null) { container.appendChild(this.node); } } }; /** * Function: initStyles * * Sets the styles to their default values. */ mxShape.prototype.initStyles = function(container) { this.strokewidth = 1; this.rotation = 0; this.opacity = 100; this.fillOpacity = 100; this.strokeOpacity = 100; this.flipH = false; this.flipV = false; }; /** * Function: isParseVml * * Specifies if any VML should be added via insertAdjacentHtml to the DOM. This * is only needed in IE8 and only if the shape contains VML markup. This method * returns true. */ mxShape.prototype.isParseVml = function() { return true; }; /** * Function: isHtmlAllowed * * Returns true if HTML is allowed for this shape. This implementation always * returns false. */ mxShape.prototype.isHtmlAllowed = function() { return false; }; /** * Function: getSvgScreenOffset * * Returns 0, or 0.5 if % 2 == 1. */ mxShape.prototype.getSvgScreenOffset = function() { var sw = this.stencil && this.stencil.strokewidth != 'inherit' ? Number(this.stencil.strokewidth) : this.strokewidth; return (mxUtils.mod(Math.max(1, Math.round(sw * this.scale)), 2) == 1) ? 0.5 : 0; }; /** * Function: create * * Creates and returns the DOM node(s) for the shape in * the given container. This implementation invokes * , or depending * on the and style settings. * * Parameters: * * container - DOM node that will contain the shape. */ mxShape.prototype.create = function(container) { var node = null; if (container != null && container.ownerSVGElement != null) { node = this.createSvg(container); } else if (document.documentMode == 8 || !mxClient.IS_VML || (this.dialect != mxConstants.DIALECT_VML && this.isHtmlAllowed())) { node = this.createHtml(container); } else { node = this.createVml(container); } return node; }; /** * Function: createSvg * * Creates and returns the SVG node(s) to represent this shape. */ mxShape.prototype.createSvg = function() { return document.createElementNS(mxConstants.NS_SVG, 'g'); }; /** * Function: createVml * * Creates and returns the VML node to represent this shape. */ mxShape.prototype.createVml = function() { var node = document.createElement(mxClient.VML_PREFIX + ':group'); node.style.position = 'absolute'; return node; }; /** * Function: createHtml * * Creates and returns the HTML DOM node(s) to represent * this shape. This implementation falls back to * so that the HTML creation is optional. */ mxShape.prototype.createHtml = function() { var node = document.createElement('div'); node.style.position = 'absolute'; return node; }; /** * Function: reconfigure * * Reconfigures this shape. This will update the colors etc in * addition to the bounds or points. */ mxShape.prototype.reconfigure = function() { this.redraw(); }; /** * Function: redraw * * Creates and returns the SVG node(s) to represent this shape. */ mxShape.prototype.redraw = function() { this.updateBoundsFromPoints(); if (this.visible && this.checkBounds()) { this.node.style.visibility = 'visible'; this.clear(); if (this.node.nodeName == 'DIV' && (this.isHtmlAllowed() || !mxClient.IS_VML)) { this.redrawHtmlShape(); } else { this.redrawShape(); } this.updateBoundingBox(); } else { this.node.style.visibility = 'hidden'; this.boundingBox = null; } }; /** * Function: clear * * Removes all child nodes and resets all CSS. */ mxShape.prototype.clear = function() { if (this.node.ownerSVGElement != null) { while (this.node.lastChild != null) { this.node.removeChild(this.node.lastChild); } } else { this.node.style.cssText = 'position:absolute;' + ((this.cursor != null) ? ('cursor:' + this.cursor + ';') : ''); this.node.innerHTML = ''; } }; /** * Function: updateBoundsFromPoints * * Updates the bounds based on the points. */ mxShape.prototype.updateBoundsFromPoints = function() { var pts = this.points; if (pts != null && pts.length > 0 && pts[0] != null) { this.bounds = new mxRectangle(Number(pts[0].x), Number(pts[0].y), 1, 1); for (var i = 1; i < this.points.length; i++) { if (pts[i] != null) { this.bounds.add(new mxRectangle(Number(pts[i].x), Number(pts[i].y), 1, 1)); } } } }; /** * Function: getLabelBounds * * Returns the for the label bounds of this shape, based on the * given scaled and translated bounds of the shape. This method should not * change the rectangle in-place. This implementation returns the given rect. */ mxShape.prototype.getLabelBounds = function(rect) { var d = mxUtils.getValue(this.style, mxConstants.STYLE_DIRECTION, mxConstants.DIRECTION_EAST); var bounds = rect; // Normalizes argument for getLabelMargins hook if (d != mxConstants.DIRECTION_SOUTH && d != mxConstants.DIRECTION_NORTH && this.state != null && this.state.text != null && this.state.text.isPaintBoundsInverted()) { bounds = bounds.clone(); var tmp = bounds.width; bounds.width = bounds.height; bounds.height = tmp; } var m = this.getLabelMargins(bounds); if (m != null) { var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPH, false) == '1'; var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_FLIPV, false) == '1'; // Handles special case for vertical labels if (this.state != null && this.state.text != null && this.state.text.isPaintBoundsInverted()) { var tmp = m.x; m.x = m.height; m.height = m.width; m.width = m.y; m.y = tmp; tmp = flipH; flipH = flipV; flipV = tmp; } return mxUtils.getDirectedBounds(rect, m, this.style, flipH, flipV); } return rect; }; /** * Function: getLabelMargins * * Returns the scaled top, left, bottom and right margin to be used for * computing the label bounds as an , where the bottom and right * margin are defined in the width and height of the rectangle, respectively. */ mxShape.prototype.getLabelMargins= function(rect) { return null; }; /** * Function: checkBounds * * Returns true if the bounds are not null and all of its variables are numeric. */ mxShape.prototype.checkBounds = function() { return (!isNaN(this.scale) && isFinite(this.scale) && this.scale > 0 && this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) && !isNaN(this.bounds.width) && !isNaN(this.bounds.height) && this.bounds.width > 0 && this.bounds.height > 0); }; /** * Function: createVmlGroup * * Returns the temporary element used for rendering in IE8 standards mode. */ mxShape.prototype.createVmlGroup = function() { var node = document.createElement(mxClient.VML_PREFIX + ':group'); node.style.position = 'absolute'; node.style.width = this.node.style.width; node.style.height = this.node.style.height; return node; }; /** * Function: redrawShape * * Updates the SVG or VML shape. */ mxShape.prototype.redrawShape = function() { var canvas = this.createCanvas(); if (canvas != null) { // Specifies if events should be handled canvas.pointerEvents = this.pointerEvents; this.paint(canvas); if (this.node != canvas.root) { // Forces parsing in IE8 standards mode - slow! avoid this.node.insertAdjacentHTML('beforeend', canvas.root.outerHTML); } if (this.node.nodeName == 'DIV' && document.documentMode == 8) { // Makes DIV transparent to events for IE8 in IE8 standards // mode (Note: Does not work for IE9 in IE8 standards mode // and not for IE11 in enterprise mode) this.node.style.filter = ''; // Adds event transparency in IE8 standards mxUtils.addTransparentBackgroundFilter(this.node); } this.destroyCanvas(canvas); } }; /** * Function: createCanvas * * Creates a new canvas for drawing this shape. May return null. */ mxShape.prototype.createCanvas = function() { var canvas = null; // LATER: Check if reusing existing DOM nodes improves performance if (this.node.ownerSVGElement != null) { canvas = this.createSvgCanvas(); } else if (mxClient.IS_VML) { this.updateVmlContainer(); canvas = this.createVmlCanvas(); } if (canvas != null && this.outline) { canvas.setStrokeWidth(this.strokewidth); canvas.setStrokeColor(this.stroke); if (this.isDashed != null) { canvas.setDashed(this.isDashed); } canvas.setStrokeWidth = function() {}; canvas.setStrokeColor = function() {}; canvas.setFillColor = function() {}; canvas.setGradient = function() {}; canvas.setDashed = function() {}; canvas.text = function() {}; } return canvas; }; /** * Function: createSvgCanvas * * Creates and returns an for rendering this shape. */ mxShape.prototype.createSvgCanvas = function() { var canvas = new mxSvgCanvas2D(this.node, false); canvas.strokeTolerance = (this.pointerEvents) ? this.svgStrokeTolerance : 0; canvas.pointerEventsValue = this.svgPointerEvents; var off = this.getSvgScreenOffset(); if (off != 0) { this.node.setAttribute('transform', 'translate(' + off + ',' + off + ')'); } else { this.node.removeAttribute('transform'); } canvas.minStrokeWidth = this.minSvgStrokeWidth; if (!this.antiAlias) { // Rounds all numbers in the SVG output to integers canvas.format = function(value) { return Math.round(parseFloat(value)); }; } return canvas; }; /** * Function: createVmlCanvas * * Creates and returns an for rendering this shape. */ mxShape.prototype.createVmlCanvas = function() { // Workaround for VML rendering bug in IE8 standards mode var node = (document.documentMode == 8 && this.isParseVml()) ? this.createVmlGroup() : this.node; var canvas = new mxVmlCanvas2D(node, false); if (node.tagUrn != '') { var w = Math.max(1, Math.round(this.bounds.width)); var h = Math.max(1, Math.round(this.bounds.height)); node.coordsize = (w * this.vmlScale) + ',' + (h * this.vmlScale); canvas.scale(this.vmlScale); canvas.vmlScale = this.vmlScale; } // Painting relative to top, left shape corner var s = this.scale; canvas.translate(-Math.round(this.bounds.x / s), -Math.round(this.bounds.y / s)); return canvas; }; /** * Function: updateVmlContainer * * Updates the bounds of the VML container. */ mxShape.prototype.updateVmlContainer = function() { this.node.style.left = Math.round(this.bounds.x) + 'px'; this.node.style.top = Math.round(this.bounds.y) + 'px'; var w = Math.max(1, Math.round(this.bounds.width)); var h = Math.max(1, Math.round(this.bounds.height)); this.node.style.width = w + 'px'; this.node.style.height = h + 'px'; this.node.style.overflow = 'visible'; }; /** * Function: redrawHtml * * Allow optimization by replacing VML with HTML. */ mxShape.prototype.redrawHtmlShape = function() { // LATER: Refactor methods this.updateHtmlBounds(this.node); this.updateHtmlFilters(this.node); this.updateHtmlColors(this.node); }; /** * Function: updateHtmlFilters * * Allow optimization by replacing VML with HTML. */ mxShape.prototype.updateHtmlFilters = function(node) { var f = ''; if (this.opacity < 100) { f += 'alpha(opacity=' + (this.opacity) + ')'; } if (this.isShadow) { // FIXME: Cannot implement shadow transparency with filter f += 'progid:DXImageTransform.Microsoft.dropShadow (' + 'OffX=\'' + Math.round(mxConstants.SHADOW_OFFSET_X * this.scale) + '\', ' + 'OffY=\'' + Math.round(mxConstants.SHADOW_OFFSET_Y * this.scale) + '\', ' + 'Color=\'' + mxConstants.VML_SHADOWCOLOR + '\')'; } if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE) { var start = this.fill; var end = this.gradient; var type = '0'; var lookup = {east:0,south:1,west:2,north:3}; var dir = (this.direction != null) ? lookup[this.direction] : 0; if (this.gradientDirection != null) { dir = mxUtils.mod(dir + lookup[this.gradientDirection] - 1, 4); } if (dir == 1) { type = '1'; var tmp = start; start = end; end = tmp; } else if (dir == 2) { var tmp = start; start = end; end = tmp; } else if (dir == 3) { type = '1'; } f += 'progid:DXImageTransform.Microsoft.gradient(' + 'startColorStr=\'' + start + '\', endColorStr=\'' + end + '\', gradientType=\'' + type + '\')'; } node.style.filter = f; }; /** * Function: mixedModeHtml * * Allow optimization by replacing VML with HTML. */ mxShape.prototype.updateHtmlColors = function(node) { var color = this.stroke; if (color != null && color != mxConstants.NONE) { node.style.borderColor = color; if (this.isDashed) { node.style.borderStyle = 'dashed'; } else if (this.strokewidth > 0) { node.style.borderStyle = 'solid'; } node.style.borderWidth = Math.max(1, Math.ceil(this.strokewidth * this.scale)) + 'px'; } else { node.style.borderWidth = '0px'; } color = (this.outline) ? null : this.fill; if (color != null && color != mxConstants.NONE) { node.style.backgroundColor = color; node.style.backgroundImage = 'none'; } else if (this.pointerEvents) { node.style.backgroundColor = 'transparent'; } else if (document.documentMode == 8) { mxUtils.addTransparentBackgroundFilter(node); } else { this.setTransparentBackgroundImage(node); } }; /** * Function: mixedModeHtml * * Allow optimization by replacing VML with HTML. */ mxShape.prototype.updateHtmlBounds = function(node) { var sw = (document.documentMode >= 9) ? 0 : Math.ceil(this.strokewidth * this.scale); node.style.borderWidth = Math.max(1, sw) + 'px'; node.style.overflow = 'hidden'; node.style.left = Math.round(this.bounds.x - sw / 2) + 'px'; node.style.top = Math.round(this.bounds.y - sw / 2) + 'px'; if (document.compatMode == 'CSS1Compat') { sw = -sw; } node.style.width = Math.round(Math.max(0, this.bounds.width + sw)) + 'px'; node.style.height = Math.round(Math.max(0, this.bounds.height + sw)) + 'px'; }; /** * Function: destroyCanvas * * Destroys the given canvas which was used for drawing. This implementation * increments the reference counts on all shared gradients used in the canvas. */ mxShape.prototype.destroyCanvas = function(canvas) { // Manages reference counts if (canvas instanceof mxSvgCanvas2D) { // Increments ref counts for (var key in canvas.gradients) { var gradient = canvas.gradients[key]; if (gradient != null) { gradient.mxRefCount = (gradient.mxRefCount || 0) + 1; } } this.releaseSvgGradients(this.oldGradients); this.oldGradients = canvas.gradients; } }; /** * Function: paint * * Generic rendering code. */ mxShape.prototype.paint = function(c) { var strokeDrawn = false; if (c != null && this.outline) { var stroke = c.stroke; c.stroke = function() { strokeDrawn = true; stroke.apply(this, arguments); }; var fillAndStroke = c.fillAndStroke; c.fillAndStroke = function() { strokeDrawn = true; fillAndStroke.apply(this, arguments); }; } // Scale is passed-through to canvas var s = this.scale; var x = this.bounds.x / s; var y = this.bounds.y / s; var w = this.bounds.width / s; var h = this.bounds.height / s; if (this.isPaintBoundsInverted()) { var t = (w - h) / 2; x += t; y -= t; var tmp = w; w = h; h = tmp; } this.updateTransform(c, x, y, w, h); this.configureCanvas(c, x, y, w, h); // Adds background rectangle to capture events var bg = null; if ((this.stencil == null && this.points == null && this.shapePointerEvents) || (this.stencil != null && this.stencilPointerEvents)) { var bb = this.createBoundingBox(); if (this.dialect == mxConstants.DIALECT_SVG) { bg = this.createTransparentSvgRectangle(bb.x, bb.y, bb.width, bb.height); this.node.appendChild(bg); } else { var rect = c.createRect('rect', bb.x / s, bb.y / s, bb.width / s, bb.height / s); rect.appendChild(c.createTransparentFill()); rect.stroked = 'false'; c.root.appendChild(rect); } } if (this.stencil != null) { this.stencil.drawShape(c, this, x, y, w, h); } else { // Stencils have separate strokewidth c.setStrokeWidth(this.strokewidth); if (this.points != null) { // Paints edge shape var pts = []; for (var i = 0; i < this.points.length; i++) { if (this.points[i] != null) { pts.push(new mxPoint(this.points[i].x / s, this.points[i].y / s)); } } this.paintEdgeShape(c, pts); } else { // Paints vertex shape this.paintVertexShape(c, x, y, w, h); } } if (bg != null && c.state != null && c.state.transform != null) { bg.setAttribute('transform', c.state.transform); } // Draws highlight rectangle if no stroke was used if (c != null && this.outline && !strokeDrawn) { c.rect(x, y, w, h); c.stroke(); } }; /** * Function: configureCanvas * * Sets the state of the canvas for drawing the shape. */ mxShape.prototype.configureCanvas = function(c, x, y, w, h) { var dash = null; if (this.style != null) { dash = this.style['dashPattern']; } c.setAlpha(this.opacity / 100); c.setFillAlpha(this.fillOpacity / 100); c.setStrokeAlpha(this.strokeOpacity / 100); // Sets alpha, colors and gradients if (this.isShadow != null) { c.setShadow(this.isShadow); } // Dash pattern if (this.isDashed != null) { c.setDashed(this.isDashed, (this.style != null) ? mxUtils.getValue(this.style, mxConstants.STYLE_FIX_DASH, false) == 1 : false); } if (dash != null) { c.setDashPattern(dash); } if (this.fill != null && this.fill != mxConstants.NONE && this.gradient && this.gradient != mxConstants.NONE) { var b = this.getGradientBounds(c, x, y, w, h); c.setGradient(this.fill, this.gradient, b.x, b.y, b.width, b.height, this.gradientDirection); } else { c.setFillColor(this.fill); } c.setStrokeColor(this.stroke); }; /** * Function: getGradientBounds * * Returns the bounding box for the gradient box for this shape. */ mxShape.prototype.getGradientBounds = function(c, x, y, w, h) { return new mxRectangle(x, y, w, h); }; /** * Function: updateTransform * * Sets the scale and rotation on the given canvas. */ mxShape.prototype.updateTransform = function(c, x, y, w, h) { // NOTE: Currently, scale is implemented in state and canvas. This will // move to canvas in a later version, so that the states are unscaled // and untranslated and do not need an update after zooming or panning. c.scale(this.scale); c.rotate(this.getShapeRotation(), this.flipH, this.flipV, x + w / 2, y + h / 2); }; /** * Function: paintVertexShape * * Paints the vertex shape. */ mxShape.prototype.paintVertexShape = function(c, x, y, w, h) { this.paintBackground(c, x, y, w, h); if (!this.outline || this.style == null || mxUtils.getValue( this.style, mxConstants.STYLE_BACKGROUND_OUTLINE, 0) == 0) { c.setShadow(false); this.paintForeground(c, x, y, w, h); } }; /** * Function: paintBackground * * Hook for subclassers. This implementation is empty. */ mxShape.prototype.paintBackground = function(c, x, y, w, h) { }; /** * Function: paintForeground * * Hook for subclassers. This implementation is empty. */ mxShape.prototype.paintForeground = function(c, x, y, w, h) { }; /** * Function: paintEdgeShape * * Hook for subclassers. This implementation is empty. */ mxShape.prototype.paintEdgeShape = function(c, pts) { }; /** * Function: getArcSize * * Returns the arc size for the given dimension. */ mxShape.prototype.getArcSize = function(w, h) { var r = 0; if (mxUtils.getValue(this.style, mxConstants.STYLE_ABSOLUTE_ARCSIZE, 0) == '1') { r = Math.min(w / 2, Math.min(h / 2, mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.LINE_ARCSIZE) / 2)); } else { var f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100; r = Math.min(w * f, h * f); } return r; }; /** * Function: paintGlassEffect * * Paints the glass gradient effect. */ mxShape.prototype.paintGlassEffect = function(c, x, y, w, h, arc) { var sw = Math.ceil(this.strokewidth / 2); var size = 0.4; c.setGradient('#ffffff', '#ffffff', x, y, w, h * 0.6, 'south', 0.9, 0.1); c.begin(); arc += 2 * sw; if (this.isRounded) { c.moveTo(x - sw + arc, y - sw); c.quadTo(x - sw, y - sw, x - sw, y - sw + arc); c.lineTo(x - sw, y + h * size); c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size); c.lineTo(x + w + sw, y - sw + arc); c.quadTo(x + w + sw, y - sw, x + w + sw - arc, y - sw); } else { c.moveTo(x - sw, y - sw); c.lineTo(x - sw, y + h * size); c.quadTo(x + w * 0.5, y + h * 0.7, x + w + sw, y + h * size); c.lineTo(x + w + sw, y - sw); } c.close(); c.fill(); }; /** * Function: addPoints * * Paints the given points with rounded corners. */ mxShape.prototype.addPoints = function(c, pts, rounded, arcSize, close, exclude, initialMove) { if (pts != null && pts.length > 0) { initialMove = (initialMove != null) ? initialMove : true; var pe = pts[pts.length - 1]; // Adds virtual waypoint in the center between start and end point if (close && rounded) { pts = pts.slice(); var p0 = pts[0]; var wp = new mxPoint(pe.x + (p0.x - pe.x) / 2, pe.y + (p0.y - pe.y) / 2); pts.splice(0, 0, wp); } var pt = pts[0]; var i = 1; // Draws the line segments if (initialMove) { c.moveTo(pt.x, pt.y); } else { c.lineTo(pt.x, pt.y); } while (i < ((close) ? pts.length : pts.length - 1)) { var tmp = pts[mxUtils.mod(i, pts.length)]; var dx = pt.x - tmp.x; var dy = pt.y - tmp.y; if (rounded && (dx != 0 || dy != 0) && (exclude == null || mxUtils.indexOf(exclude, i - 1) < 0)) { // Draws a line from the last point to the current // point with a spacing of size off the current point // into direction of the last point var dist = Math.sqrt(dx * dx + dy * dy); var nx1 = dx * Math.min(arcSize, dist / 2) / dist; var ny1 = dy * Math.min(arcSize, dist / 2) / dist; var x1 = tmp.x + nx1; var y1 = tmp.y + ny1; c.lineTo(x1, y1); // Draws a curve from the last point to the current // point with a spacing of size off the current point // into direction of the next point var next = pts[mxUtils.mod(i + 1, pts.length)]; // Uses next non-overlapping point while (i < pts.length - 2 && Math.round(next.x - tmp.x) == 0 && Math.round(next.y - tmp.y) == 0) { next = pts[mxUtils.mod(i + 2, pts.length)]; i++; } dx = next.x - tmp.x; dy = next.y - tmp.y; dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)); var nx2 = dx * Math.min(arcSize, dist / 2) / dist; var ny2 = dy * Math.min(arcSize, dist / 2) / dist; var x2 = tmp.x + nx2; var y2 = tmp.y + ny2; c.quadTo(tmp.x, tmp.y, x2, y2); tmp = new mxPoint(x2, y2); } else { c.lineTo(tmp.x, tmp.y); } pt = tmp; i++; } if (close) { c.close(); } else { c.lineTo(pe.x, pe.y); } } }; /** * Function: resetStyles * * Resets all styles. */ mxShape.prototype.resetStyles = function() { this.initStyles(); this.spacing = 0; delete this.fill; delete this.gradient; delete this.gradientDirection; delete this.stroke; delete this.startSize; delete this.endSize; delete this.startArrow; delete this.endArrow; delete this.direction; delete this.isShadow; delete this.isDashed; delete this.isRounded; delete this.glass; }; /** * Function: apply * * Applies the style of the given to the shape. This * implementation assigns the following styles to local fields: * * - => fill * - => gradient * - => gradientDirection * - => opacity * - => fillOpacity * - => strokeOpacity * - => stroke * - => strokewidth * - => isShadow * - => isDashed * - => spacing * - => startSize * - => endSize * - => isRounded * - => startArrow * - => endArrow * - => rotation * - => direction * - => glass * * This keeps a reference to the '); * }; * (end) * * Headers: * * Apart from setting the title argument in the mxPrintPreview constructor you * can override as follows to add a header to any page: * * (code) * var oldRenderPage = mxPrintPreview.prototype.renderPage; * mxPrintPreview.prototype.renderPage = function(w, h, x, y, content, pageNumber) * { * var div = oldRenderPage.apply(this, arguments); * * var header = document.createElement('div'); * header.style.position = 'absolute'; * header.style.top = '0px'; * header.style.width = '100%'; * header.style.textAlign = 'right'; * mxUtils.write(header, 'Your header here'); * div.firstChild.appendChild(header); * * return div; * }; * (end) * * The pageNumber argument contains the number of the current page, starting at * 1. To display a header on the first page only, check pageNumber and add a * vertical offset in the constructor call for the height of the header. * * Page Format: * * For landscape printing, use as * the pageFormat in and . * Keep in mind that one can not set the defaults for the print dialog * of the operating system from JavaScript so the user must manually choose * a page format that matches this setting. * * You can try passing the following CSS directive to to set the * page format in the print dialog to landscape. However, this CSS * directive seems to be ignored in most major browsers, including IE. * * (code) * @page { * size: landscape; * } * (end) * * Note that the print preview behaves differently in IE when used from the * filesystem or via HTTP so printing should always be tested via HTTP. * * If you are using a DOCTYPE in the source page you can override * and provide the same DOCTYPE for the print preview if required. Here is * an example for IE8 standards mode. * * (code) * var preview = new mxPrintPreview(graph); * preview.getDoctype = function() * { * return ''; * }; * preview.open(); * (end) * * Constructor: mxPrintPreview * * Constructs a new print preview for the given parameters. * * Parameters: * * graph - to be previewed. * scale - Optional scale of the output. Default is 1 / . * pageFormat - that specifies the page format (in pixels). * border - Border in pixels along each side of every page. Note that the * actual print function in the browser will add another border for * printing. * This should match the page format of the printer. Default uses the * of the given graph. * x0 - Optional left offset of the output. Default is 0. * y0 - Optional top offset of the output. Default is 0. * borderColor - Optional color of the page border. Default is no border. * Note that a border is sometimes useful to highlight the printed page * border in the print preview of the browser. * title - Optional string that is used for the window title. Default * is 'Printer-friendly version'. * pageSelector - Optional boolean that specifies if the page selector * should appear in the window with the print preview. Default is true. */ function mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector) { this.graph = graph; this.scale = (scale != null) ? scale : 1 / graph.pageScale; this.border = (border != null) ? border : 0; this.pageFormat = mxRectangle.fromRectangle((pageFormat != null) ? pageFormat : graph.pageFormat); this.title = (title != null) ? title : 'Printer-friendly version'; this.x0 = (x0 != null) ? x0 : 0; this.y0 = (y0 != null) ? y0 : 0; this.borderColor = borderColor; this.pageSelector = (pageSelector != null) ? pageSelector : true; }; /** * Variable: graph * * Reference to the that should be previewed. */ mxPrintPreview.prototype.graph = null; /** * Variable: pageFormat * * Holds the that defines the page format. */ mxPrintPreview.prototype.pageFormat = null; /** * Variable: scale * * Holds the scale of the print preview. */ mxPrintPreview.prototype.scale = null; /** * Variable: border * * The border inset around each side of every page in the preview. This is set * to 0 if autoOrigin is false. */ mxPrintPreview.prototype.border = 0; /** * Variable: marginTop * * The margin at the top of the page (number). Default is 0. */ mxPrintPreview.prototype.marginTop = 0; /** * Variable: marginBottom * * The margin at the bottom of the page (number). Default is 0. */ mxPrintPreview.prototype.marginBottom = 0; /** * Variable: x0 * * Holds the horizontal offset of the output. */ mxPrintPreview.prototype.x0 = 0; /** * Variable: y0 * * Holds the vertical offset of the output. */ mxPrintPreview.prototype.y0 = 0; /** * Variable: autoOrigin * * Specifies if the origin should be automatically computed based on the top, * left corner of the actual diagram contents. The required offset will be added * to and in . Default is true. */ mxPrintPreview.prototype.autoOrigin = true; /** * Variable: printOverlays * * Specifies if overlays should be printed. Default is false. */ mxPrintPreview.prototype.printOverlays = false; /** * Variable: printControls * * Specifies if controls (such as folding icons) should be printed. Default is * false. */ mxPrintPreview.prototype.printControls = false; /** * Variable: printBackgroundImage * * Specifies if the background image should be printed. Default is false. */ mxPrintPreview.prototype.printBackgroundImage = false; /** * Variable: backgroundColor * * Holds the color value for the page background color. Default is #ffffff. */ mxPrintPreview.prototype.backgroundColor = '#ffffff'; /** * Variable: borderColor * * Holds the color value for the page border. */ mxPrintPreview.prototype.borderColor = null; /** * Variable: title * * Holds the title of the preview window. */ mxPrintPreview.prototype.title = null; /** * Variable: pageSelector * * Boolean that specifies if the page selector should be * displayed. Default is true. */ mxPrintPreview.prototype.pageSelector = null; /** * Variable: wnd * * Reference to the preview window. */ mxPrintPreview.prototype.wnd = null; /** * Variable: targetWindow * * Assign any window here to redirect the rendering in . */ mxPrintPreview.prototype.targetWindow = null; /** * Variable: pageCount * * Holds the actual number of pages in the preview. */ mxPrintPreview.prototype.pageCount = 0; /** * Variable: clipping * * Specifies is clipping should be used to avoid creating too many cell states * in large diagrams. The bounding box of the cells in the original diagram is * used if this is enabled. Default is true. */ mxPrintPreview.prototype.clipping = true; /** * Function: getWindow * * Returns . */ mxPrintPreview.prototype.getWindow = function() { return this.wnd; }; /** * Function: getDocType * * Returns the string that should go before the HTML tag in the print preview * page. This implementation returns an X-UA meta tag for IE5 in quirks mode, * IE8 in IE8 standards mode and edge in IE9 standards mode. */ mxPrintPreview.prototype.getDoctype = function() { var dt = ''; if (document.documentMode == 5) { dt = ''; } else if (document.documentMode == 8) { dt = ''; } else if (document.documentMode > 8) { // Comment needed to make standards doctype apply in IE dt = ''; } return dt; }; /** * Function: appendGraph * * Adds the given graph to the existing print preview. * * Parameters: * * css - Optional CSS string to be used in the head section. * targetWindow - Optional window that should be used for rendering. If * this is specified then no HEAD tag, CSS and BODY tag will be written. */ mxPrintPreview.prototype.appendGraph = function(graph, scale, x0, y0, forcePageBreaks, keepOpen) { this.graph = graph; this.scale = (scale != null) ? scale : 1 / graph.pageScale; this.x0 = x0; this.y0 = y0; this.open(null, null, forcePageBreaks, keepOpen); }; /** * Function: open * * Shows the print preview window. The window is created here if it does * not exist. * * Parameters: * * css - Optional CSS string to be used in the head section. * targetWindow - Optional window that should be used for rendering. If * this is specified then no HEAD tag, CSS and BODY tag will be written. */ mxPrintPreview.prototype.open = function(css, targetWindow, forcePageBreaks, keepOpen) { // Closing the window while the page is being rendered may cause an // exception in IE. This and any other exceptions are simply ignored. var previousInitializeOverlay = this.graph.cellRenderer.initializeOverlay; var div = null; try { // Temporarily overrides the method to redirect rendering of overlays // to the draw pane so that they are visible in the printout if (this.printOverlays) { this.graph.cellRenderer.initializeOverlay = function(state, overlay) { overlay.init(state.view.getDrawPane()); }; } if (this.printControls) { this.graph.cellRenderer.initControl = function(state, control, handleEvents, clickHandler) { control.dialect = state.view.graph.dialect; control.init(state.view.getDrawPane()); }; } this.wnd = (targetWindow != null) ? targetWindow : this.wnd; var isNewWindow = false; if (this.wnd == null) { isNewWindow = true; this.wnd = window.open(); } var doc = this.wnd.document; if (isNewWindow) { var dt = this.getDoctype(); if (dt != null && dt.length > 0) { doc.writeln(dt); } if (mxClient.IS_VML) { doc.writeln(''); } else { if (document.compatMode === 'CSS1Compat') { doc.writeln(''); } doc.writeln(''); } doc.writeln(''); this.writeHead(doc, css); doc.writeln(''); doc.writeln(''); } // Computes the horizontal and vertical page count var bounds = this.graph.getGraphBounds().clone(); var currentScale = this.graph.getView().getScale(); var sc = currentScale / this.scale; var tr = this.graph.getView().getTranslate(); // Uses the absolute origin with no offset for all printing if (!this.autoOrigin) { this.x0 -= tr.x * this.scale; this.y0 -= tr.y * this.scale; bounds.width += bounds.x; bounds.height += bounds.y; bounds.x = 0; bounds.y = 0; this.border = 0; } // Store the available page area var availableWidth = this.pageFormat.width - (this.border * 2); var availableHeight = this.pageFormat.height - (this.border * 2); // Adds margins to page format this.pageFormat.height += this.marginTop + this.marginBottom; // Compute the unscaled, untranslated bounds to find // the number of vertical and horizontal pages bounds.width /= sc; bounds.height /= sc; var hpages = Math.max(1, Math.ceil((bounds.width + this.x0) / availableWidth)); var vpages = Math.max(1, Math.ceil((bounds.height + this.y0) / availableHeight)); this.pageCount = hpages * vpages; var writePageSelector = mxUtils.bind(this, function() { if (this.pageSelector && (vpages > 1 || hpages > 1)) { var table = this.createPageSelector(vpages, hpages); doc.body.appendChild(table); // Implements position: fixed in IE quirks mode if (mxClient.IS_IE && doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7) { table.style.position = 'absolute'; var update = function() { table.style.top = ((doc.body.scrollTop || doc.documentElement.scrollTop) + 10) + 'px'; }; mxEvent.addListener(this.wnd, 'scroll', function(evt) { update(); }); mxEvent.addListener(this.wnd, 'resize', function(evt) { update(); }); } } }); var addPage = mxUtils.bind(this, function(div, addBreak) { // Border of the DIV (aka page) inside the document if (this.borderColor != null) { div.style.borderColor = this.borderColor; div.style.borderStyle = 'solid'; div.style.borderWidth = '1px'; } // Needs to be assigned directly because IE doesn't support // child selectors, eg. body > div { background: white; } div.style.background = this.backgroundColor; if (forcePageBreaks || addBreak) { div.style.pageBreakAfter = 'always'; } // NOTE: We are dealing with cross-window DOM here, which // is a problem in IE, so we copy the HTML markup instead. // The underlying problem is that the graph display markup // creation (in mxShape, mxGraphView) is hardwired to using // document.createElement and hence we must use this document // to create the complete page and then copy it over to the // new window.document. This can be fixed later by using the // ownerDocument of the container in mxShape and mxGraphView. if (isNewWindow && (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE)) { // For some obscure reason, removing the DIV from the // parent before fetching its outerHTML has missing // fillcolor properties and fill children, so the div // must be removed afterwards to keep the fillcolors. doc.writeln(div.outerHTML); div.parentNode.removeChild(div); } else if (mxClient.IS_IE || document.documentMode >= 11 || mxClient.IS_EDGE) { var clone = doc.createElement('div'); clone.innerHTML = div.outerHTML; clone = clone.getElementsByTagName('div')[0]; doc.body.appendChild(clone); div.parentNode.removeChild(div); } else { div.parentNode.removeChild(div); doc.body.appendChild(div); } if (forcePageBreaks || addBreak) { this.addPageBreak(doc); } }); var cov = this.getCoverPages(this.pageFormat.width, this.pageFormat.height); if (cov != null) { for (var i = 0; i < cov.length; i++) { addPage(cov[i], true); } } var apx = this.getAppendices(this.pageFormat.width, this.pageFormat.height); // Appends each page to the page output for printing, making // sure there will be a page break after each page (ie. div) for (var i = 0; i < vpages; i++) { var dy = i * availableHeight / this.scale - this.y0 / this.scale + (bounds.y - tr.y * currentScale) / currentScale; for (var j = 0; j < hpages; j++) { if (this.wnd == null) { return null; } var dx = j * availableWidth / this.scale - this.x0 / this.scale + (bounds.x - tr.x * currentScale) / currentScale; var pageNum = i * hpages + j + 1; var clip = new mxRectangle(dx, dy, availableWidth, availableHeight); div = this.renderPage(this.pageFormat.width, this.pageFormat.height, 0, 0, mxUtils.bind(this, function(div) { this.addGraphFragment(-dx, -dy, this.scale, pageNum, div, clip); if (this.printBackgroundImage) { this.insertBackgroundImage(div, -dx, -dy); } }), pageNum); // Gives the page a unique ID for later accessing the page div.setAttribute('id', 'mxPage-'+pageNum); addPage(div, apx != null || i < vpages - 1 || j < hpages - 1); } } if (apx != null) { for (var i = 0; i < apx.length; i++) { addPage(apx[i], i < apx.length - 1); } } if (isNewWindow && !keepOpen) { this.closeDocument(); writePageSelector(); } this.wnd.focus(); } catch (e) { // Removes the DIV from the document in case of an error if (div != null && div.parentNode != null) { div.parentNode.removeChild(div); } } finally { this.graph.cellRenderer.initializeOverlay = previousInitializeOverlay; } return this.wnd; }; /** * Function: addPageBreak * * Adds a page break to the given document. */ mxPrintPreview.prototype.addPageBreak = function(doc) { var hr = doc.createElement('hr'); hr.className = 'mxPageBreak'; doc.body.appendChild(hr); }; /** * Function: closeDocument * * Writes the closing tags for body and page after calling . */ mxPrintPreview.prototype.closeDocument = function() { try { if (this.wnd != null && this.wnd.document != null) { var doc = this.wnd.document; this.writePostfix(doc); doc.writeln(''); doc.writeln(''); doc.close(); // Removes all event handlers in the print output mxEvent.release(doc.body); } } catch (e) { // ignore any errors resulting from wnd no longer being available } }; /** * Function: writeHead * * Writes the HEAD section into the given document, without the opening * and closing HEAD tags. */ mxPrintPreview.prototype.writeHead = function(doc, css) { if (this.title != null) { doc.writeln('' + this.title + ''); } // Adds required namespaces if (mxClient.IS_VML) { doc.writeln(''); } // Adds all required stylesheets mxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc); // Removes horizontal rules and page selector from print output doc.writeln(''); }; /** * Function: writePostfix * * Called before closing the body of the page. This implementation is empty. */ mxPrintPreview.prototype.writePostfix = function(doc) { // empty }; /** * Function: createPageSelector * * Creates the page selector table. */ mxPrintPreview.prototype.createPageSelector = function(vpages, hpages) { var doc = this.wnd.document; var table = doc.createElement('table'); table.className = 'mxPageSelector'; table.setAttribute('border', '0'); var tbody = doc.createElement('tbody'); for (var i = 0; i < vpages; i++) { var row = doc.createElement('tr'); for (var j = 0; j < hpages; j++) { var pageNum = i * hpages + j + 1; var cell = doc.createElement('td'); var a = doc.createElement('a'); a.setAttribute('href', '#mxPage-' + pageNum); // Workaround for FF where the anchor is appended to the URL of the original document if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC) { var js = 'var page = document.getElementById(\'mxPage-' + pageNum + '\');page.scrollIntoView(true);event.preventDefault();'; a.setAttribute('onclick', js); } mxUtils.write(a, pageNum, doc); cell.appendChild(a); row.appendChild(cell); } tbody.appendChild(row); } table.appendChild(tbody); return table; }; /** * Function: renderPage * * Creates a DIV that prints a single page of the given * graph using the given scale and returns the DIV that * represents the page. * * Parameters: * * w - Width of the page in pixels. * h - Height of the page in pixels. * dx - Optional horizontal page offset in pixels (used internally). * dy - Optional vertical page offset in pixels (used internally). * content - Callback that adds the HTML content to the inner div of a page. * Takes the inner div as the argument. * pageNumber - Integer representing the page number. */ mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, content, pageNumber) { var doc = this.wnd.document; var div = document.createElement('div'); var arg = null; try { // Workaround for ignored clipping in IE 9 standards // when printing with page breaks and HTML labels. if (dx != 0 || dy != 0) { div.style.position = 'relative'; div.style.width = w + 'px'; div.style.height = h + 'px'; div.style.pageBreakInside = 'avoid'; var innerDiv = document.createElement('div'); innerDiv.style.position = 'relative'; innerDiv.style.top = this.border + 'px'; innerDiv.style.left = this.border + 'px'; innerDiv.style.width = (w - 2 * this.border) + 'px'; innerDiv.style.height = (h - 2 * this.border) + 'px'; innerDiv.style.overflow = 'hidden'; var viewport = document.createElement('div'); viewport.style.position = 'relative'; viewport.style.marginLeft = dx + 'px'; viewport.style.marginTop = dy + 'px'; // FIXME: IE8 standards output problems if (doc.documentMode == 8) { innerDiv.style.position = 'absolute'; viewport.style.position = 'absolute'; } if (doc.documentMode == 10) { viewport.style.width = '100%'; viewport.style.height = '100%'; } innerDiv.appendChild(viewport); div.appendChild(innerDiv); document.body.appendChild(div); arg = viewport; } // FIXME: IE10/11 too many pages else { div.style.width = w + 'px'; div.style.height = h + 'px'; div.style.overflow = 'hidden'; div.style.pageBreakInside = 'avoid'; // IE8 uses above branch currently if (doc.documentMode == 8) { div.style.position = 'relative'; } var innerDiv = document.createElement('div'); innerDiv.style.width = (w - 2 * this.border) + 'px'; innerDiv.style.height = (h - 2 * this.border) + 'px'; innerDiv.style.overflow = 'hidden'; if (mxClient.IS_IE && (doc.documentMode == null || doc.documentMode == 5 || doc.documentMode == 8 || doc.documentMode == 7)) { innerDiv.style.marginTop = this.border + 'px'; innerDiv.style.marginLeft = this.border + 'px'; } else { innerDiv.style.top = this.border + 'px'; innerDiv.style.left = this.border + 'px'; } if (this.graph.dialect == mxConstants.DIALECT_VML) { innerDiv.style.position = 'absolute'; } div.appendChild(innerDiv); document.body.appendChild(div); arg = innerDiv; } } catch (e) { div.parentNode.removeChild(div); div = null; throw e; } content(arg); return div; }; /** * Function: getRoot * * Returns the root cell for painting the graph. */ mxPrintPreview.prototype.getRoot = function() { var root = this.graph.view.currentRoot; if (root == null) { root = this.graph.getModel().getRoot(); } return root; }; /** * Function: addGraphFragment * * Adds a graph fragment to the given div. * * Parameters: * * dx - Horizontal translation for the diagram. * dy - Vertical translation for the diagram. * scale - Scale for the diagram. * pageNumber - Number of the page to be rendered. * div - Div that contains the output. * clip - Contains the clipping rectangle as an . */ mxPrintPreview.prototype.addGraphFragment = function(dx, dy, scale, pageNumber, div, clip) { var view = this.graph.getView(); var previousContainer = this.graph.container; this.graph.container = div; var canvas = view.getCanvas(); var backgroundPane = view.getBackgroundPane(); var drawPane = view.getDrawPane(); var overlayPane = view.getOverlayPane(); var realScale = scale; if (this.graph.dialect == mxConstants.DIALECT_SVG) { view.createSvg(); // Uses CSS transform for scaling if (!mxClient.NO_FO) { var g = view.getDrawPane().parentNode; var prev = g.getAttribute('transform'); g.setAttribute('transformOrigin', '0 0'); g.setAttribute('transform', 'scale(' + scale + ',' + scale + ')' + 'translate(' + dx + ',' + dy + ')'); scale = 1; dx = 0; dy = 0; } } else if (this.graph.dialect == mxConstants.DIALECT_VML) { view.createVml(); } else { view.createHtml(); } // Disables events on the view var eventsEnabled = view.isEventsEnabled(); view.setEventsEnabled(false); // Disables the graph to avoid cursors var graphEnabled = this.graph.isEnabled(); this.graph.setEnabled(false); // Resets the translation var translate = view.getTranslate(); view.translate = new mxPoint(dx, dy); // Redraws only states that intersect the clip var redraw = this.graph.cellRenderer.redraw; var states = view.states; var s = view.scale; // Gets the transformed clip for intersection check below if (this.clipping) { var tempClip = new mxRectangle((clip.x + translate.x) * s, (clip.y + translate.y) * s, clip.width * s / realScale, clip.height * s / realScale); // Checks clipping rectangle for speedup // Must create terminal states for edge clipping even if terminal outside of clip this.graph.cellRenderer.redraw = function(state, force, rendering) { if (state != null) { // Gets original state from graph to find bounding box var orig = states.get(state.cell); if (orig != null) { var bbox = view.getBoundingBox(orig, false); // Stops rendering if outside clip for speedup but ignores // edge labels where width and height is set to 0 if (bbox != null && bbox.width > 0 && bbox.height > 0 && !mxUtils.intersects(tempClip, bbox)) { return; } } } redraw.apply(this, arguments); }; } var temp = null; try { // Creates the temporary cell states in the view and // draws them onto the temporary DOM nodes in the view var cells = [this.getRoot()]; temp = new mxTemporaryCellStates(view, scale, cells, null, mxUtils.bind(this, function(state) { return this.getLinkForCellState(state); })); } finally { // Removes overlay pane with selection handles // controls and icons from the print output if (mxClient.IS_IE) { view.overlayPane.innerHTML = ''; view.canvas.style.overflow = 'hidden'; view.canvas.style.position = 'relative'; view.canvas.style.top = this.marginTop + 'px'; view.canvas.style.width = clip.width + 'px'; view.canvas.style.height = clip.height + 'px'; } else { // Removes everything but the SVG node var tmp = div.firstChild; while (tmp != null) { var next = tmp.nextSibling; var name = tmp.nodeName.toLowerCase(); // Note: Width and height are required in FF 11 if (name == 'svg') { tmp.style.overflow = 'hidden'; tmp.style.position = 'relative'; tmp.style.top = this.marginTop + 'px'; tmp.setAttribute('width', clip.width); tmp.setAttribute('height', clip.height); tmp.style.width = ''; tmp.style.height = ''; } // Tries to fetch all text labels and only text labels else if (tmp.style.cursor != 'default' && name != 'div') { tmp.parentNode.removeChild(tmp); } tmp = next; } } // Puts background image behind SVG output if (this.printBackgroundImage) { var svgs = div.getElementsByTagName('svg'); if (svgs.length > 0) { svgs[0].style.position = 'absolute'; } } // Completely removes the overlay pane to remove more handles view.overlayPane.parentNode.removeChild(view.overlayPane); // Restores the state of the view this.graph.setEnabled(graphEnabled); this.graph.container = previousContainer; this.graph.cellRenderer.redraw = redraw; view.canvas = canvas; view.backgroundPane = backgroundPane; view.drawPane = drawPane; view.overlayPane = overlayPane; view.translate = translate; temp.destroy(); view.setEventsEnabled(eventsEnabled); } }; /** * Function: getLinkForCellState * * Returns the link for the given cell state. This returns null. */ mxPrintPreview.prototype.getLinkForCellState = function(state) { return this.graph.getLinkForCell(state.cell); }; /** * Function: insertBackgroundImage * * Inserts the background image into the given div. */ mxPrintPreview.prototype.insertBackgroundImage = function(div, dx, dy) { var bg = this.graph.backgroundImage; if (bg != null) { var img = document.createElement('img'); img.style.position = 'absolute'; img.style.marginLeft = Math.round(dx * this.scale) + 'px'; img.style.marginTop = Math.round(dy * this.scale) + 'px'; img.setAttribute('width', Math.round(this.scale * bg.width)); img.setAttribute('height', Math.round(this.scale * bg.height)); img.src = bg.src; div.insertBefore(img, div.firstChild); } }; /** * Function: getCoverPages * * Returns the pages to be added before the print output. This returns null. */ mxPrintPreview.prototype.getCoverPages = function() { return null; }; /** * Function: getAppendices * * Returns the pages to be added after the print output. This returns null. */ mxPrintPreview.prototype.getAppendices = function() { return null; }; /** * Function: print * * Opens the print preview and shows the print dialog. * * Parameters: * * css - Optional CSS string to be used in the head section. */ mxPrintPreview.prototype.print = function(css) { var wnd = this.open(css); if (wnd != null) { wnd.print(); } }; /** * Function: close * * Closes the print preview window. */ mxPrintPreview.prototype.close = function() { if (this.wnd != null) { this.wnd.close(); this.wnd = null; } }; /** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * Class: mxStylesheet * * Defines the appearance of the cells in a graph. See for an * example of creating a new cell style. It is recommended to use objects, not * arrays for holding cell styles. Existing styles can be cloned using * and turned into a string for debugging using * . * * Default Styles: * * The stylesheet contains two built-in styles, which are used if no style is * defined for a cell: * * defaultVertex - Default style for vertices * defaultEdge - Default style for edges * * Example: * * (code) * var vertexStyle = stylesheet.getDefaultVertexStyle(); * vertexStyle[mxConstants.STYLE_ROUNDED] = true; * var edgeStyle = stylesheet.getDefaultEdgeStyle(); * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation; * (end) * * Modifies the built-in default styles. * * To avoid the default style for a cell, add a leading semicolon * to the style definition, eg. * * (code) * ;shadow=1 * (end) * * Removing keys: * * For removing a key in a cell style of the form [stylename;|key=value;] the * special value none can be used, eg. highlight;fillColor=none * * See also the helper methods in mxUtils to modify strings of this format, * namely , , * , , * and . * * Constructor: mxStylesheet * * Constructs a new stylesheet and assigns default styles. */ function mxStylesheet() { this.styles = new Object(); this.putDefaultVertexStyle(this.createDefaultVertexStyle()); this.putDefaultEdgeStyle(this.createDefaultEdgeStyle()); }; /** * Function: styles * * Maps from names to cell styles. Each cell style is a map of key, * value pairs. */ mxStylesheet.prototype.styles; /** * Function: createDefaultVertexStyle * * Creates and returns the default vertex style. */ mxStylesheet.prototype.createDefaultVertexStyle = function() { var style = new Object(); style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE; style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER; style[mxConstants.STYLE_FILLCOLOR] = '#C3D9FF'; style[mxConstants.STYLE_STROKECOLOR] = '#6482B9'; style[mxConstants.STYLE_FONTCOLOR] = '#774400'; return style; }; /** * Function: createDefaultEdgeStyle * * Creates and returns the default edge style. */ mxStylesheet.prototype.createDefaultEdgeStyle = function() { var style = new Object(); style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR; style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC; style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE; style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER; style[mxConstants.STYLE_STROKECOLOR] = '#6482B9'; style[mxConstants.STYLE_FONTCOLOR] = '#446299'; return style; }; /** * Function: putDefaultVertexStyle * * Sets the default style for vertices using defaultVertex as the * stylename. * * Parameters: * style - Key, value pairs that define the style. */ mxStylesheet.prototype.putDefaultVertexStyle = function(style) { this.putCellStyle('defaultVertex', style); }; /** * Function: putDefaultEdgeStyle * * Sets the default style for edges using defaultEdge as the stylename. */ mxStylesheet.prototype.putDefaultEdgeStyle = function(style) { this.putCellStyle('defaultEdge', style); }; /** * Function: getDefaultVertexStyle * * Returns the default style for vertices. */ mxStylesheet.prototype.getDefaultVertexStyle = function() { return this.styles['defaultVertex']; }; /** * Function: getDefaultEdgeStyle * * Sets the default style for edges. */ mxStylesheet.prototype.getDefaultEdgeStyle = function() { return this.styles['defaultEdge']; }; /** * Function: putCellStyle * * Stores the given map of key, value pairs under the given name in * . * * Example: * * The following example adds a new style called 'rounded' into an * existing stylesheet: * * (code) * var style = new Object(); * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE; * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter; * style[mxConstants.STYLE_ROUNDED] = true; * graph.getStylesheet().putCellStyle('rounded', style); * (end) * * In the above example, the new style is an object. The possible keys of * the object are all the constants in that start with STYLE * and the values are either JavaScript objects, such as * (which is in fact a function) * or expressions, such as true. Note that not all keys will be * interpreted by all shapes (eg. the line shape ignores the fill color). * The final call to this method associates the style with a name in the * stylesheet. The style is used in a cell with the following code: * * (code) * model.setStyle(cell, 'rounded'); * (end) * * Parameters: * * name - Name for the style to be stored. * style - Key, value pairs that define the style. */ mxStylesheet.prototype.putCellStyle = function(name, style) { this.styles[name] = style; }; /** * Function: getCellStyle * * Returns the cell style for the specified stylename or the given * defaultStyle if no style can be found for the given stylename. * * Parameters: * * name - String of the form [(stylename|key=value);] that represents the * style. * defaultStyle - Default style to be returned if no style can be found. */ mxStylesheet.prototype.getCellStyle = function(name, defaultStyle) { var style = defaultStyle; if (name != null && name.length > 0) { var pairs = name.split(';'); if (style != null && name.charAt(0) != ';') { style = mxUtils.clone(style); } else { style = new Object(); } // Parses each key, value pair into the existing style for (var i = 0; i < pairs.length; i++) { var tmp = pairs[i]; var pos = tmp.indexOf('='); if (pos >= 0) { var key = tmp.substring(0, pos); var value = tmp.substring(pos + 1); if (value == mxConstants.NONE) { delete style[key]; } else if (mxUtils.isNumeric(value)) { style[key] = parseFloat(value); } else { style[key] = value; } } else { // Merges the entries from a named style var tmpStyle = this.styles[tmp]; if (tmpStyle != null) { for (var key in tmpStyle) { style[key] = tmpStyle[key]; } } } } } return style; }; /** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * Class: mxCellState * * Represents the current state of a cell in a given . * * For edges, the edge label position is stored in . * * The size for oversize labels can be retrieved using the boundingBox property * of the field as shown below. * * (code) * var bbox = (state.text != null) ? state.text.boundingBox : null; * (end) * * Constructor: mxCellState * * Constructs a new object that represents the current state of the given * cell in the specified view. * * Parameters: * * view - that contains the state. * cell - that this state represents. * style - Array of key, value pairs that constitute the style. */ function mxCellState(view, cell, style) { this.view = view; this.cell = cell; this.style = (style != null) ? style : {}; this.origin = new mxPoint(); this.absoluteOffset = new mxPoint(); }; /** * Extends mxRectangle. */ mxCellState.prototype = new mxRectangle(); mxCellState.prototype.constructor = mxCellState; /** * Variable: view * * Reference to the enclosing . */ mxCellState.prototype.view = null; /** * Variable: cell * * Reference to the that is represented by this state. */ mxCellState.prototype.cell = null; /** * Variable: style * * Contains an array of key, value pairs that represent the style of the * cell. */ mxCellState.prototype.style = null; /** * Variable: invalidStyle * * Specifies if the style is invalid. Default is false. */ mxCellState.prototype.invalidStyle = false; /** * Variable: invalid * * Specifies if the state is invalid. Default is true. */ mxCellState.prototype.invalid = true; /** * Variable: origin * * that holds the origin for all child cells. Default is a new * empty . */ mxCellState.prototype.origin = null; /** * Variable: absolutePoints * * Holds an array of that represent the absolute points of an * edge. */ mxCellState.prototype.absolutePoints = null; /** * Variable: absoluteOffset * * that holds the absolute offset. For edges, this is the * absolute coordinates of the label position. For vertices, this is the * offset of the label relative to the top, left corner of the vertex. */ mxCellState.prototype.absoluteOffset = null; /** * Variable: visibleSourceState * * Caches the visible source terminal state. */ mxCellState.prototype.visibleSourceState = null; /** * Variable: visibleTargetState * * Caches the visible target terminal state. */ mxCellState.prototype.visibleTargetState = null; /** * Variable: terminalDistance * * Caches the distance between the end points for an edge. */ mxCellState.prototype.terminalDistance = 0; /** * Variable: length * * Caches the length of an edge. */ mxCellState.prototype.length = 0; /** * Variable: segments * * Array of numbers that represent the cached length of each segment of the * edge. */ mxCellState.prototype.segments = null; /** * Variable: shape * * Holds the that represents the cell graphically. */ mxCellState.prototype.shape = null; /** * Variable: text * * Holds the that represents the label of the cell. Thi smay be * null if the cell has no label. */ mxCellState.prototype.text = null; /** * Variable: unscaledWidth * * Holds the unscaled width of the state. */ mxCellState.prototype.unscaledWidth = null; /** * Variable: unscaledHeight * * Holds the unscaled height of the state. */ mxCellState.prototype.unscaledHeight = null; /** * Function: getPerimeterBounds * * Returns the that should be used as the perimeter of the * cell. * * Parameters: * * border - Optional border to be added around the perimeter bounds. * bounds - Optional to be used as the initial bounds. */ mxCellState.prototype.getPerimeterBounds = function(border, bounds) { border = border || 0; bounds = (bounds != null) ? bounds : new mxRectangle(this.x, this.y, this.width, this.height); if (this.shape != null && this.shape.stencil != null && this.shape.stencil.aspect == 'fixed') { var aspect = this.shape.stencil.computeAspect(this.style, bounds.x, bounds.y, bounds.width, bounds.height); bounds.x = aspect.x; bounds.y = aspect.y; bounds.width = this.shape.stencil.w0 * aspect.width; bounds.height = this.shape.stencil.h0 * aspect.height; } if (border != 0) { bounds.grow(border); } return bounds; }; /** * Function: setAbsoluteTerminalPoint * * Sets the first or last point in depending on isSource. * * Parameters: * * point - that represents the terminal point. * isSource - Boolean that specifies if the first or last point should * be assigned. */ mxCellState.prototype.setAbsoluteTerminalPoint = function(point, isSource) { if (isSource) { if (this.absolutePoints == null) { this.absolutePoints = []; } if (this.absolutePoints.length == 0) { this.absolutePoints.push(point); } else { this.absolutePoints[0] = point; } } else { if (this.absolutePoints == null) { this.absolutePoints = []; this.absolutePoints.push(null); this.absolutePoints.push(point); } else if (this.absolutePoints.length == 1) { this.absolutePoints.push(point); } else { this.absolutePoints[this.absolutePoints.length - 1] = point; } } }; /** * Function: setCursor * * Sets the given cursor on the shape and text shape. */ mxCellState.prototype.setCursor = function(cursor) { if (this.shape != null) { this.shape.setCursor(cursor); } if (this.text != null) { this.text.setCursor(cursor); } }; /** * Function: getVisibleTerminal * * Returns the visible source or target terminal cell. * * Parameters: * * source - Boolean that specifies if the source or target cell should be * returned. */ mxCellState.prototype.getVisibleTerminal = function(source) { var tmp = this.getVisibleTerminalState(source); return (tmp != null) ? tmp.cell : null; }; /** * Function: getVisibleTerminalState * * Returns the visible source or target terminal state. * * Parameters: * * source - Boolean that specifies if the source or target state should be * returned. */ mxCellState.prototype.getVisibleTerminalState = function(source) { return (source) ? this.visibleSourceState : this.visibleTargetState; }; /** * Function: setVisibleTerminalState * * Sets the visible source or target terminal state. * * Parameters: * * terminalState - that represents the terminal. * source - Boolean that specifies if the source or target state should be set. */ mxCellState.prototype.setVisibleTerminalState = function(terminalState, source) { if (source) { this.visibleSourceState = terminalState; } else { this.visibleTargetState = terminalState; } }; /** * Function: getCellBounds * * Returns the unscaled, untranslated bounds. */ mxCellState.prototype.getCellBounds = function() { return this.cellBounds; }; /** * Function: getPaintBounds * * Returns the unscaled, untranslated paint bounds. This is the same as * but with a 90 degree rotation if the shape's * isPaintBoundsInverted returns true. */ mxCellState.prototype.getPaintBounds = function() { return this.paintBounds; }; /** * Function: updateCachedBounds * * Updates the cellBounds and paintBounds. */ mxCellState.prototype.updateCachedBounds = function() { var tr = this.view.translate; var s = this.view.scale; this.cellBounds = new mxRectangle(this.x / s - tr.x, this.y / s - tr.y, this.width / s, this.height / s); this.paintBounds = mxRectangle.fromRectangle(this.cellBounds); if (this.shape != null && this.shape.isPaintBoundsInverted()) { this.paintBounds.rotate90(); } }; /** * Destructor: setState * * Copies all fields from the given state to this state. */ mxCellState.prototype.setState = function(state) { this.view = state.view; this.cell = state.cell; this.style = state.style; this.absolutePoints = state.absolutePoints; this.origin = state.origin; this.absoluteOffset = state.absoluteOffset; this.boundingBox = state.boundingBox; this.terminalDistance = state.terminalDistance; this.segments = state.segments; this.length = state.length; this.x = state.x; this.y = state.y; this.width = state.width; this.height = state.height; this.unscaledWidth = state.unscaledWidth; this.unscaledHeight = state.unscaledHeight; }; /** * Function: clone * * Returns a clone of this . */ mxCellState.prototype.clone = function() { var clone = new mxCellState(this.view, this.cell, this.style); // Clones the absolute points if (this.absolutePoints != null) { clone.absolutePoints = []; for (var i = 0; i < this.absolutePoints.length; i++) { clone.absolutePoints[i] = this.absolutePoints[i].clone(); } } if (this.origin != null) { clone.origin = this.origin.clone(); } if (this.absoluteOffset != null) { clone.absoluteOffset = this.absoluteOffset.clone(); } if (this.boundingBox != null) { clone.boundingBox = this.boundingBox.clone(); } clone.terminalDistance = this.terminalDistance; clone.segments = this.segments; clone.length = this.length; clone.x = this.x; clone.y = this.y; clone.width = this.width; clone.height = this.height; clone.unscaledWidth = this.unscaledWidth; clone.unscaledHeight = this.unscaledHeight; return clone; }; /** * Destructor: destroy * * Destroys the state and all associated resources. */ mxCellState.prototype.destroy = function() { this.view.graph.cellRenderer.destroy(this); }; /** * Copyright (c) 2006-2015, JGraph Ltd * Copyright (c) 2006-2015, Gaudenz Alder */ /** * Class: mxGraphSelectionModel * * Implements the selection model for a graph. Here is a listener that handles * all removed selection cells. * * (code) * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt) * { * var cells = evt.getProperty('added'); * * for (var i = 0; i < cells.length; i++) * { * // Handle cells[i]... * } * }); * (end) * * Event: mxEvent.UNDO * * Fires after the selection was changed in . The * edit property contains the which contains the * . * * Event: mxEvent.CHANGE * * Fires after the selection changes by executing an . The * added and removed properties contain arrays of * cells that have been added to or removed from the selection, respectively. * The names are inverted due to historic reasons. This cannot be changed. * * Constructor: mxGraphSelectionModel * * Constructs a new graph selection model for the given . * * Parameters: * * graph - Reference to the enclosing . */ function mxGraphSelectionModel(graph) { this.graph = graph; this.cells = []; }; /** * Extends mxEventSource. */ mxGraphSelectionModel.prototype = new mxEventSource(); mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel; /** * Variable: doneResource * * Specifies the resource key for the status message after a long operation. * If the resource for this key does not exist then the value is used as * the status message. Default is 'done'. */ mxGraphSelectionModel.prototype.doneResource = (mxClient.language != 'none') ? 'done' : ''; /** * Variable: updatingSelectionResource * * Specifies the resource key for the status message while the selection is * being updated. If the resource for this key does not exist then the * value is used as the status message. Default is 'updatingSelection'. */ mxGraphSelectionModel.prototype.updatingSelectionResource = (mxClient.language != 'none') ? 'updatingSelection' : ''; /** * Variable: graph * * Reference to the enclosing . */ mxGraphSelectionModel.prototype.graph = null; /** * Variable: singleSelection * * Specifies if only one selected item at a time is allowed. * Default is false. */ mxGraphSelectionModel.prototype.singleSelection = false; /** * Function: isSingleSelection * * Returns as a boolean. */ mxGraphSelectionModel.prototype.isSingleSelection = function() { return this.singleSelection; }; /** * Function: setSingleSelection * * Sets the flag. * * Parameters: * * singleSelection - Boolean that specifies the new value for * . */ mxGraphSelectionModel.prototype.setSingleSelection = function(singleSelection) { this.singleSelection = singleSelection; }; /** * Function: isSelected * * Returns true if the given is selected. */ mxGraphSelectionModel.prototype.isSelected = function(cell) { if (cell != null) { return mxUtils.indexOf(this.cells, cell) >= 0; } return false; }; /** * Function: isEmpty * * Returns true if no cells are currently selected. */ mxGraphSelectionModel.prototype.isEmpty = function() { return this.cells.length == 0; }; /** * Function: clear * * Clears the selection and fires a event if the selection was not * empty. */ mxGraphSelectionModel.prototype.clear = function() { this.changeSelection(null, this.cells); }; /** * Function: setCell * * Selects the specified using . * * Parameters: * * cell - to be selected. */ mxGraphSelectionModel.prototype.setCell = function(cell) { if (cell != null) { this.setCells([cell]); } }; /** * Function: setCells * * Selects the given array of and fires a event. * * Parameters: * * cells - Array of to be selected. */ mxGraphSelectionModel.prototype.setCells = function(cells) { if (cells != null) { if (this.singleSelection) { cells = [this.getFirstSelectableCell(cells)]; } var tmp = []; for (var i = 0; i < cells.length; i++) { if (this.graph.isCellSelectable(cells[i])) { tmp.push(cells[i]); } } this.changeSelection(tmp, this.cells); } }; /** * Function: getFirstSelectableCell * * Returns the first selectable cell in the given array of cells. */ mxGraphSelectionModel.prototype.getFirstSelectableCell = function(cells) { if (cells != null) { for (var i = 0; i < cells.length; i++) { if (this.graph.isCellSelectable(cells[i])) { return cells[i]; } } } return null; }; /** * Function: addCell * * Adds the given to the selection and fires a * event. * * Parameters: * * cells - Array of to add to the selection. */ mxGraphSelectionModel.prototype.addCells = function(cells) { if (cells != null) { var remove = null; if (this.singleSelection) { remove = this.cells; cells = [this.getFirstSelectableCell(cells)]; } var tmp = []; for (var i = 0; i < cells.length; i++) { if (!this.isSelected(cells[i]) && this.graph.isCellSelectable(cells[i])) { tmp.push(cells[i]); } } this.changeSelection(tmp, remove); } }; /** * Function: removeCell * * Removes the specified from the selection and fires a